/*------------------------------------------------------------------------------ -- This file is a part of the libuc, microcontroler library -- Copyright (C) 2012, Alexis Jeandet -- -- This program 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. -- -- 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------- -- Author : Alexis Jeandet -- Mail : alexis.jeandet@gmail.com -------------------------------------------------------------------------------*/ #include "sdcard.h" static volatile unsigned int Timer1, Timer2; /* 1kHz decrement timer stopped at zero (disk_timerproc()) */ void sdcarddeselect (blkdeviceptr _this) { char d; _this->select(0); ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,&d, 1); /* Dummy clock (force DO hi-z for multiple slave SPI) */ } int sdcardselect (blkdeviceptr _this) /* 1:OK, 0:Timeout */ { char d; _this->select(1); ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,&d, 1); /* Dummy clock (force DO enabled) */ if (sdcardwait_ready(((sdcardDev*)_this->phy))) return 1; /* OK */ _this->select(0); return 0; /* Timeout */ } int sdcardwait_ready (sdcardDev* sdcard) /* 1:OK, 0:Timeout */ { char d; unsigned int tmr; volatile unsigned int i=0; for (tmr = 100000; tmr; tmr--) { /* Wait for ready in timeout of 500ms */ sdcard->rcvr_mmc(sdcard->phy,&d, 1); if (d == 0xFF) break; for(i=0;i<128;i++); } return tmr ? 1 : 0; } int sdcardrcvr_datablock (sdcardDev* sdcard,char *buff,uint32_t btr) { char d[2]; unsigned int tmr; volatile unsigned int i=0; for (tmr = 50000; tmr; tmr--) { /* Wait for data packet in timeout of 100ms */ sdcard->rcvr_mmc(sdcard->phy,d, 1); if (d[0] != 0xFF) break; for(i=0;i<64;i++); } if (d[0] != 0xFE) return 0; /* If not valid data token, return with error */ sdcard->rcvr_mmc(sdcard->phy,buff, btr); /* Receive the data block into buffer */ sdcard->rcvr_mmc(sdcard->phy,d, 2); /* Discard CRC */ return 1; /* Return with success */ } int sdcardxmit_datablock (sdcardDev* sdcard,const char *buff,char token) { char d[2]; if (!sdcardwait_ready(sdcard)) return 0; d[0] = token; sdcard->xmit_mmc(sdcard->phy,d, 1); /* Xmit a token */ if (token != 0xFD) { /* Is it data token? */ sdcard->xmit_mmc(sdcard->phy,buff, 512); /* Xmit the 512 byte data block to MMC */ sdcard->rcvr_mmc(sdcard->phy,d, 2); /* Xmit dummy CRC (0xFF,0xFF) */ sdcard->rcvr_mmc(sdcard->phy,d, 1); /* Receive data response */ if ((d[0] & 0x1F) != 0x05) /* If not accepted, return with error */ return 0; } return 1; } char sdcardsend_cmd (blkdeviceptr _this,char cmd,uint32_t arg) { char n, d, buf[6]; if (cmd & 0x80) { /* ACMD is the command sequense of CMD55-CMD */ cmd &= 0x7F; n = sdcardsend_cmd(_this,CMD55, 0); if (n > 1) return n; } /* Select the card and wait for ready */ sdcarddeselect(_this); if (!sdcardselect(_this)) return 0xFF; /* Send a command packet */ buf[0] = 0x40 | cmd; /* Start + Command index */ buf[1] = (char)(arg >> 24); /* Argument[31..24] */ buf[2] = (char)(arg >> 16); /* Argument[23..16] */ buf[3] = (char)(arg >> 8); /* Argument[15..8] */ buf[4] = (char)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)) */ if (cmd == CMD55) n = 0x63; buf[5] = n; ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,buf, 6); /* Receive command response */ if (cmd == CMD12) {((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,&d, 1);} /* Skip a stuff byte when stop reading */ n = 10; /* Wait for a valid response in timeout of 10 attempts */ do { ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,&d, 1); //libucprintf("resp=%d\n\r",d); } while ((d & 0x80) && --n); return d; /* Return with the response value */ } DSTATUS sdcarddisk_status (blkdeviceptr _this) { DSTATUS s = ((sdcardDev*)_this->phy)->Stat; char ocr[4]; if ((_this==0) || !_this->detect()) { s = STA_NODISK | STA_NOINIT; } else { s &= ~STA_NODISK; if (_this->writeprotected()) /* Check card write protection */ s |= STA_PROTECT; else s &= ~STA_PROTECT; if (!(s & STA_NOINIT)) { if (sdcardsend_cmd(_this,CMD58, 0)) /* Check if the card is kept initialized */ s |= STA_NOINIT; ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ocr, 4); _this->select(0); } } ((sdcardDev*)_this->phy)->Stat = s; return s; } DSTATUS sdcarddisk_initialize (blkdeviceptr _this) { char cmd, ty, ocr[4],ocr2[4]; int n; const char dummy=0xff; uint32_t speed = 1000000; ((sdcardDev*)_this->phy)->setspeed(((sdcardDev*)_this->phy)->phy,350000); if (_this==0) return STA_NOINIT; /* Supports only drive 0 */ if (((sdcardDev*)_this->phy)->Stat & STA_NODISK) return ((sdcardDev*)_this->phy)->Stat; /* Is card existing in the soket? */ for (n = 10; n; n--) ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,&dummy, 1); /* Send 80 dummy clocks */ ty = 0; if (sdcardsend_cmd(_this,CMD0, 0) == 1) { /* Put the card SPI/Idle state */ Timer1 = 1000; /* Initialization timeout = 1 sec */ if (sdcardsend_cmd(_this,CMD8, 0x1AA) == 1) { /* SDv2? */ ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ocr, 4); //for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); /* Get 32 bit return value of R7 resp */ if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /* Is the card supports vcc of 2.7-3.6V? */ while (Timer1 && sdcardsend_cmd(_this,ACMD41, 1UL << 30)) ; /* Wait for end of initialization with ACMD41(HCS) */ if (Timer1 && sdcardsend_cmd(_this,CMD58, 0) == 0) { /* Check CCS bit in the OCR */ //for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF); ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ocr, 4); ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* Card id SDv2 */ } } } else { /* Not SDv2 card */ if (sdcardsend_cmd(_this,ACMD41, 0) <= 1) { /* SDv1 or MMC? */ ty = CT_SD1; cmd = ACMD41; /* SDv1 (ACMD41(0)) */ } else { ty = CT_MMC; cmd = CMD1; /* MMCv3 (CMD1(0)) */ } while (Timer1 && sdcardsend_cmd(_this,cmd, 0)) ; /* Wait for end of initialization */ if (!Timer1 || sdcardsend_cmd(_this,CMD16, 512) != 0) /* Set block length: 512 */ ty = 0; } } ((sdcardDev*)_this->phy)->CardType = ty; /* Card type */ if (ty) { /* OK */ ((sdcardDev*)_this->phy)->Stat &= ~STA_NOINIT; /* Clear STA_NOINIT flag */ sdcardsend_cmd(_this,CMD58, 0); ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ocr, 4); do { speed+=1000000; ((sdcardDev*)_this->phy)->setspeed(((sdcardDev*)_this->phy)->phy,speed); sdcardsend_cmd(_this,CMD58, 0); ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ocr2, 4); for(n=0;n<4;n++) { if(ocr[n]!=ocr2[n]) { n=0; break; } } if(speed>(50*1000*1000)) { break; } }while(n); if(!n) { speed-=1000000; ((sdcardDev*)_this->phy)->setspeed(((sdcardDev*)_this->phy)->phy,speed); } } else { /* Failed */ //power_off(); ((sdcardDev*)_this->phy)->Stat = STA_NOINIT; } sdcarddeselect (_this); return ((sdcardDev*)_this->phy)->Stat; } DRESULT sdcarddisk_read (blkdeviceptr _this,char *buff,uint32_t sector,char count) { DSTATUS s; s = sdcarddisk_status(_this); if (s & STA_NOINIT) return RES_NOTRDY; if (!count) return RES_PARERR; if (!(((sdcardDev*)_this->phy)->CardType & CT_BLOCK)) sector *= 512; /* Convert LBA to byte address if needed */ if (count == 1) { /* Single block read */ if ((sdcardsend_cmd(_this,CMD17, sector) == 0) && sdcardrcvr_datablock((sdcardDev*)(_this->phy),buff, 512)) count = 0; } else { /* Multiple block read */ if (sdcardsend_cmd(_this,CMD18, sector) == 0) { /* READ_MULTIPLE_BLOCK */ do { if (!sdcardrcvr_datablock (((sdcardDev*)_this->phy),buff, 512)) break; buff += 512; } while (--count); sdcardsend_cmd(_this,CMD12, 0); /* STOP_TRANSMISSION */ } } sdcarddeselect (_this); return count ? RES_ERROR : RES_OK; } DRESULT sdcarddisk_write (blkdeviceptr _this,const char *buff,uint32_t sector,char count) { DSTATUS s; s = sdcarddisk_status(_this); if (s & STA_NOINIT) return RES_NOTRDY; if (s & STA_PROTECT) return RES_WRPRT; if (!count) return RES_PARERR; if (!(((sdcardDev*)_this->phy)->CardType & CT_BLOCK)) sector *= 512; /* Convert LBA to byte address if needed */ if (count == 1) { /* Single block write */ if ((sdcardsend_cmd(_this,CMD24, sector) == 0) /* WRITE_BLOCK */ && sdcardxmit_datablock (((sdcardDev*)_this->phy),buff, 0xFE)) count = 0; } else { /* Multiple block write */ if (((sdcardDev*)_this->phy)->CardType & CT_SDC) sdcardsend_cmd(_this,ACMD23, count); if (sdcardsend_cmd(_this,CMD25, sector) == 0) { /* WRITE_MULTIPLE_BLOCK */ do { if (!sdcardxmit_datablock (((sdcardDev*)_this->phy),buff, 0xFC)) break; buff += 512; } while (--count); if (!sdcardxmit_datablock (((sdcardDev*)_this->phy),0, 0xFD)) /* STOP_TRAN token */ count = 1; } } sdcarddeselect (_this); return count ? RES_ERROR : RES_OK; } DRESULT sdcarddisk_ioctl (blkdeviceptr _this,char ctrl,void *buff) { DRESULT res; const char dummy=0xff; char n, csd[16], *ptr = buff; uint16_t csize; uint32_t *dp, st, ed; if (_this==0) return RES_PARERR; /* Check parameter */ if (((sdcardDev*)_this->phy)->Stat & STA_NOINIT) return RES_NOTRDY; /* Check if drive is ready */ res = RES_ERROR; switch (ctrl) { case CTRL_SYNC : /* Wait for end of internal write process of the drive */ if (sdcardselect (_this)) { sdcarddeselect (_this); res = RES_OK; } break; case GET_SECTOR_COUNT : /* Get drive capacity in unit of sector (DWORD) */ if ((sdcardsend_cmd(_this,CMD9, 0) == 0) && sdcardrcvr_datablock (((sdcardDev*)_this->phy),csd, 16)) { if ((csd[0] >> 6) == 1) { /* SDC ver 2.00 */ csize = csd[9] + ((uint16_t)csd[8] << 8) + 1; *(uint32_t*)buff = (uint32_t)csize << 10; } else { /* SDC ver 1.XX or MMC ver 3 */ n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1; *(uint32_t*)buff = (uint32_t)csize << (n - 9); } res = RES_OK; } break; case GET_SECTOR_SIZE : /* Get sector size in unit of byte (WORD) */ *(uint16_t*)buff = 512; res = RES_OK; break; case GET_BLOCK_SIZE : /* Get erase block size in unit of sector (DWORD) */ if (((sdcardDev*)_this->phy)->CardType & CT_SD2) { /* SDC ver 2.00 */ if (sdcardsend_cmd(_this,ACMD13, 0) == 0) { /* Read SD status */ ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,&dummy, 1); if (sdcardrcvr_datablock (((sdcardDev*)_this->phy),csd, 16)) { /* Read partial block */ for (n = 64 - 16; n; n--) ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,&dummy, 1); /* Purge trailing data */ *(uint32_t*)buff = 16UL << (csd[10] >> 4); res = RES_OK; ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,&dummy, 1); } } } else { /* SDC ver 1.XX or MMC */ if ((sdcardsend_cmd(_this,CMD9, 0) == 0) && sdcardrcvr_datablock (((sdcardDev*)_this->phy),csd, 16)) { /* Read CSD */ if (((sdcardDev*)_this->phy)->CardType & CT_SD1) { /* SDC ver 1.XX */ *(uint32_t*)buff = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); } else { /* MMC */ *(uint32_t*)buff = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); } res = RES_OK; } } break; case CTRL_ERASE_SECTOR : /* Erase a block of sectors (used when _USE_ERASE == 1) */ if (!(((sdcardDev*)_this->phy)->CardType & CT_SDC)) break; /* Check if the card is SDC */ if (sdcarddisk_ioctl(_this, MMC_GET_CSD, csd)) break; /* Get CSD */ if (!(csd[0] >> 6) && !(csd[10] & 0x40)) break; /* Check if sector erase can be applied to the card */ dp = buff; st = dp[0]; ed = dp[1]; /* Load sector block */ if (!(((sdcardDev*)_this->phy)->CardType & CT_BLOCK)) { st *= 512; ed *= 512; } if (sdcardsend_cmd(_this,CMD32, st) == 0 && sdcardsend_cmd(_this,CMD33, ed) == 0 && sdcardsend_cmd(_this,CMD38, 0) == 0 && sdcardwait_ready(((sdcardDev*)_this->phy))) /* Erase sector block */ res = RES_OK; /* FatFs does not check result of this command */ break; /* Following command are not used by FatFs module */ case MMC_GET_TYPE : /* Get MMC/SDC type (BYTE) */ *ptr = ((sdcardDev*)_this->phy)->CardType; res = RES_OK; break; case MMC_GET_CSD : /* Read CSD (16 bytes) */ if (sdcardsend_cmd(_this,CMD9, 0) == 0 /* READ_CSD */ && sdcardrcvr_datablock (((sdcardDev*)_this->phy),ptr, 16)) res = RES_OK; break; case MMC_GET_CID : /* Read CID (16 bytes) */ if (sdcardsend_cmd(_this,CMD10, 0) == 0 /* READ_CID */ && sdcardrcvr_datablock (((sdcardDev*)_this->phy),ptr, 16)) res = RES_OK; break; case MMC_GET_OCR : /* Read OCR (4 bytes) */ if (sdcardsend_cmd(_this,CMD58, 0) == 0) { /* READ_OCR */ //for (n = 4; n; n--) *ptr++ = ((sdcardDev*)_this->phy)->rcvr_mmc(((sdcardDev*)_this->phy)->phy,ptr, 4); res = RES_OK; } break; case MMC_GET_SDSTAT : /* Read SD status (64 bytes) */ if (sdcardsend_cmd(_this,ACMD13, 0) == 0) { /* SD_STATUS */ ((sdcardDev*)_this->phy)->xmit_mmc(((sdcardDev*)_this->phy)->phy,&dummy, 1); if (sdcardrcvr_datablock (((sdcardDev*)_this->phy),ptr, 64)) res = RES_OK; } break; default: res = RES_PARERR; } sdcarddeselect (_this); return res; } void sdcardmake(sdcardDev* sdcard,UHANDLE phy,void (*rcvr_mmc) (UHANDLE,char *,uint32_t),void (*xmit_mmc) (UHANDLE,const char *,uint32_t ),void (*setspeed) (UHANDLE phy,uint32_t speed),uint32_t (*getspeed) (UHANDLE phy)) { sdcard->phy = phy; sdcard->rcvr_mmc = rcvr_mmc; sdcard->xmit_mmc = xmit_mmc; sdcard->setspeed = setspeed; sdcard->getspeed = getspeed; } void sdcardmakeblkdev(blkdevice* dev,sdcardDev* sdcard, blkdevselect_t select,blkdevpower_t power,blkdevdetect_t detect,blkdevwriteprotected_t writeprotected) { dev->phy=sdcard; dev->select=select; dev->power = power; dev->detect = detect; dev->writeprotected = writeprotected; dev->write = sdcarddisk_write; dev->read = sdcarddisk_read; dev->ioctl = sdcarddisk_ioctl; dev->initialize = sdcarddisk_initialize; dev->status = sdcarddisk_status; }