OpenRTX/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.cpp

1777 lines
58 KiB
C++

/***************************************************************************
* Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico *
* *
* 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 2 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. *
* *
* As a special exception, if other files instantiate templates or use *
* macros or inline functions from this file, or you compile this file *
* and link it with other works to produce a work based on this file, *
* this file does not by itself cause the resulting work to be covered *
* by the GNU General Public License. However the source code for this *
* file must still be made available in accordance with the GNU General *
* Public License. This exception does not invalidate any other reasons *
* why a work based on this file might be covered by the GNU General *
* Public License. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "sd_stm32f1.h"
#include "interfaces/bsp.h"
#include "interfaces/arch_registers.h"
#include "interfaces/delays.h"
#include "kernel/kernel.h"
#include "kernel/scheduler/scheduler.h"
#include "board_settings.h" //For sdVoltage
#include <cstdio>
#include <cstring>
#include <errno.h>
/*
* This driver is quite a bit complicated, due to a silicon errata in the
* STM32F1 microcontrollers, that prevents concurrent access to the FSMC
* (i.e., the external memory controller) by both the CPU and DMA.
* Therefore, if __ENABLE_XRAM is defined, the SDIO peripheral is used in
* polled mode, otherwise in DMA mode. The use in polled mode is further
* complicated by the fact that the SDIO peripheral does not halt the clock
* to the SD card if its internal fifo is full. Therefore, when using the
* SDIO in polled mode the only solution is to disable interrupts during
* the data transfer. To optimize reading and writing speed this code
* automatically chooses the best transfer speed using a binary search during
* card initialization. Also, other sources of mess are the requirement for
* word alignment of pointers when doing DMA transfers or writing to the SDIO
* peripheral. Because of that, tryng to fwrite() large bloks of data is faster
* if they are word aligned. An easy way to do so is to allocate them on the
* heap (and not doing any pointer arithmetic on the value returned by
* malloc/new)
*/
//Note: enabling debugging might cause deadlock when using sleep() or reboot()
//The bug won't be fixed because debugging is only useful for driver development
///\internal Debug macro, for normal conditions
//#define DBG iprintf
#define DBG(x,...) do {} while(0)
///\internal Debug macro, for errors only
//#define DBGERR iprintf
#define DBGERR(x,...) do {} while(0)
#ifndef __ENABLE_XRAM
/**
* \internal
* DMA2 Channel4 interrupt handler
*/
void __attribute__((naked)) DMA2_Channel4_5_IRQHandler()
{
saveContext();
asm volatile("bl _ZN6miosix19DMA2channel4irqImplEv");
restoreContext();
}
/**
* \internal
* SDIO interrupt handler
*/
void __attribute__((naked)) SDIO_IRQHandler()
{
saveContext();
asm volatile("bl _ZN6miosix11SDIOirqImplEv");
restoreContext();
}
#endif //__ENABLE_XRAM
namespace miosix {
#ifndef __ENABLE_XRAM
static volatile bool transferError; ///< \internal DMA or SDIO transfer error
static Thread *waiting; ///< \internal Thread waiting for transfer
static unsigned int dmaFlags; ///< \internal DMA status flags
static unsigned int sdioFlags; ///< \internal SDIO status flags
/**
* \internal
* DMA2 Channel4 interrupt handler actual implementation
*/
void __attribute__((used)) DMA2channel4irqImpl()
{
dmaFlags=DMA2->ISR;
if(dmaFlags & DMA_ISR_TEIF4) transferError=true;
DMA2->IFCR=DMA_IFCR_CGIF4;
if(!waiting) return;
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=0;
}
/**
* \internal
* DMA2 Channel4 interrupt handler actual implementation
*/
void __attribute__((used)) SDIOirqImpl()
{
sdioFlags=SDIO->STA;
if(sdioFlags & (SDIO_STA_STBITERR | SDIO_STA_RXOVERR |
SDIO_STA_TXUNDERR | SDIO_STA_DTIMEOUT | SDIO_STA_DCRCFAIL))
transferError=true;
SDIO->ICR=0x7ff;//Clear flags
if(!waiting) return;
waiting->IRQwakeup();
if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
Scheduler::IRQfindNextThread();
waiting=0;
}
#endif //__ENABLE_XRAM
/*
* Operating voltage of device. It is sent to the SD card to check if it can
* work at this voltage. Range *must* be within 28..36
* Example 33=3.3v
*/
//const unsigned char sdVoltage=33; //Is defined in board_settings.h
const unsigned int sdVoltageMask=1<<(sdVoltage-13); //See OCR register in SD spec
/**
* \internal
* Possible state of the cardType variable.
*/
enum CardType
{
Invalid=0, ///<\internal Invalid card type
MMC=1<<0, ///<\internal if(cardType==MMC) card is an MMC
SDv1=1<<1, ///<\internal if(cardType==SDv1) card is an SDv1
SDv2=1<<2, ///<\internal if(cardType==SDv2) card is an SDv2
SDHC=1<<3 ///<\internal if(cardType==SDHC) card is an SDHC
};
///\internal Type of card.
static CardType cardType=Invalid;
//SD card GPIOs
typedef Gpio<GPIOC_BASE,8> sdD0;
typedef Gpio<GPIOC_BASE,9> sdD1;
typedef Gpio<GPIOC_BASE,10> sdD2;
typedef Gpio<GPIOC_BASE,11> sdD3;
typedef Gpio<GPIOC_BASE,12> sdCLK;
typedef Gpio<GPIOD_BASE,2> sdCMD;
//
// Class BufferConverter
//
/**
* \internal
* Convert a single buffer of *fixed* and predetermined size to and from
* word-aligned. To do so, if the buffer is already word aligned a cast is made,
* otherwise a new buffer is allocated.
* Note that this class allocates at most ONE buffer at any given time.
* Therefore any call to toWordAligned(), toWordAlignedWithoutCopy(),
* toOriginalBuffer() or deallocateBuffer() invalidates the buffer previousy
* returned by toWordAligned() and toWordAlignedWithoutCopy()
*/
class BufferConverter
{
public:
/**
* \internal
* The buffer will be of this size only.
*/
static const int BUFFER_SIZE=512;
/**
* \internal
* \return true if the pointer is word aligned
*/
static bool isWordAligned(const void *x)
{
return (reinterpret_cast<const unsigned int>(x) & 0x3)==0;
}
/**
* \internal
* Convert from a constunsigned char* buffer of size BUFFER_SIZE to a
* const unsigned int* word aligned buffer.
* If the original buffer is already word aligned it only does a cast,
* otherwise it copies the data on the original buffer to a word aligned
* buffer. Useful if subseqent code will read from the buffer.
* \param a buffer of size BUFFER_SIZE. Can be word aligned or not.
* \return a word aligned buffer with the same data of the given buffer
*/
static const unsigned int *toWordAligned(const unsigned char *buffer);
/**
* \internal
* Convert from an unsigned char* buffer of size BUFFER_SIZE to an
* unsigned int* word aligned buffer.
* If the original buffer is already word aligned it only does a cast,
* otherwise it returns a new buffer which *does not* contain the data
* on the original buffer. Useful if subseqent code will write to the
* buffer. To move the written data to the original buffer, use
* toOriginalBuffer()
* \param a buffer of size BUFFER_SIZE. Can be word aligned or not.
* \return a word aligned buffer with undefined content.
*/
static unsigned int *toWordAlignedWithoutCopy(unsigned char *buffer);
/**
* \internal
* Convert the buffer got through toWordAlignedWithoutCopy() to the
* original buffer. If the original buffer was word aligned, nothing
* happens, otherwise a memcpy is done.
* Note that this function does not work on buffers got through
* toWordAligned().
*/
static void toOriginalBuffer();
/**
* \internal
* Can be called to deallocate the buffer
*/
static void deallocateBuffer();
private:
static unsigned char *originalBuffer;
static unsigned int *wordAlignedBuffer;
};
const unsigned int *BufferConverter::toWordAligned(const unsigned char *buffer)
{
originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do
if(isWordAligned(buffer))
{
return reinterpret_cast<const unsigned int*>(buffer);
} else {
if(wordAlignedBuffer==0)
wordAlignedBuffer=new unsigned int[BUFFER_SIZE/sizeof(unsigned int)];
std::memcpy(wordAlignedBuffer,buffer,BUFFER_SIZE);
return wordAlignedBuffer;
}
}
unsigned int *BufferConverter::toWordAlignedWithoutCopy(
unsigned char *buffer)
{
if(isWordAligned(buffer))
{
originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do
return reinterpret_cast<unsigned int*>(buffer);
} else {
originalBuffer=buffer; //Save original pointer for toOriginalBuffer()
if(wordAlignedBuffer==0)
wordAlignedBuffer=new unsigned int[BUFFER_SIZE/sizeof(unsigned int)];
return wordAlignedBuffer;
}
}
void BufferConverter::toOriginalBuffer()
{
if(originalBuffer==0) return;
std::memcpy(originalBuffer,wordAlignedBuffer,BUFFER_SIZE);
originalBuffer=0;
}
void BufferConverter::deallocateBuffer()
{
originalBuffer=0; //Invalidate also original buffer
if(wordAlignedBuffer!=0)
{
delete[] wordAlignedBuffer;
wordAlignedBuffer=0;
}
}
unsigned char *BufferConverter::originalBuffer=0;
unsigned int *BufferConverter::wordAlignedBuffer=0;
//
// Class CmdResult
//
/**
* \internal
* Contains the result of an SD/MMC command
*/
class CmdResult
{
public:
/**
* \internal
* Possible outcomes of sending a command
*/
enum Error
{
Ok=0, /// No errors
Timeout, /// Timeout while waiting command reply
CRCFail, /// CRC check failed in command reply
RespNotMatch,/// Response index does not match command index
ACMDFail /// Sending CMD55 failed
};
/**
* \internal
* Default constructor
*/
CmdResult(): cmd(0), error(Ok), response(0) {}
/**
* \internal
* Constructor, set the response data
* \param cmd command index of command that was sent
* \param result result of command
*/
CmdResult(unsigned char cmd, Error error): cmd(cmd), error(error),
response(SDIO->RESP1) {}
/**
* \internal
* \return the 32 bit of the response.
* May not be valid if getError()!=Ok or the command does not send a
* response, such as CMD0
*/
unsigned int getResponse() { return response; }
/**
* \internal
* \return command index
*/
unsigned char getCmdIndex() { return cmd; }
/**
* \internal
* \return the error flags of the response
*/
Error getError() { return error; }
/**
* \internal
* Checks if errors occurred while sending the command.
* \return true if no errors, false otherwise
*/
bool validateError();
/**
* \internal
* interprets this->getResponse() as an R1 response, and checks if there are
* errors, or everything is ok
* \return true on success, false on failure
*/
bool validateR1Response();
/**
* \internal
* Same as validateR1Response, but can be called with interrupts disabled.
* \return true on success, false on failure
*/
bool IRQvalidateR1Response();
/**
* \internal
* interprets this->getResponse() as an R6 response, and checks if there are
* errors, or everything is ok
* \return true on success, false on failure
*/
bool validateR6Response();
/**
* \internal
* \return the card state from an R1 or R6 resonse
*/
unsigned char getState();
private:
unsigned char cmd; ///<\internal Command index that was sent
Error error; ///<\internal possible error that occurred
unsigned int response; ///<\internal 32bit response
};
bool CmdResult::validateError()
{
switch(error)
{
case Ok:
return true;
case Timeout:
DBGERR("CMD%d: Timeout\n",cmd);
break;
case CRCFail:
DBGERR("CMD%d: CRC Fail\n",cmd);
break;
case RespNotMatch:
DBGERR("CMD%d: Response does not match\n",cmd);
break;
case ACMDFail:
DBGERR("CMD%d: ACMD Fail\n",cmd);
break;
}
return false;
}
bool CmdResult::validateR1Response()
{
if(error!=Ok) return validateError();
//Note: this number is obtained with all the flags of R1 which are errors
//(flagged as E in the SD specification), plus CARD_IS_LOCKED because
//locked card are not supported by this software driver
if((response & 0xfff98008)==0) return true;
DBGERR("CMD%d: R1 response error(s):\n",cmd);
if(response & (1<<31)) DBGERR("Out of range\n");
if(response & (1<<30)) DBGERR("ADDR error\n");
if(response & (1<<29)) DBGERR("BLOCKLEN error\n");
if(response & (1<<28)) DBGERR("ERASE SEQ error\n");
if(response & (1<<27)) DBGERR("ERASE param\n");
if(response & (1<<26)) DBGERR("WP violation\n");
if(response & (1<<25)) DBGERR("card locked\n");
if(response & (1<<24)) DBGERR("LOCK_UNLOCK failed\n");
if(response & (1<<23)) DBGERR("command CRC failed\n");
if(response & (1<<22)) DBGERR("illegal command\n");
if(response & (1<<21)) DBGERR("ECC fail\n");
if(response & (1<<20)) DBGERR("card controller error\n");
if(response & (1<<19)) DBGERR("unknown error\n");
if(response & (1<<16)) DBGERR("CSD overwrite\n");
if(response & (1<<15)) DBGERR("WP ERASE skip\n");
if(response & (1<<3)) DBGERR("AKE_SEQ error\n");
return false;
}
bool CmdResult::IRQvalidateR1Response()
{
if(error!=Ok) return false;
if(response & 0xfff98008) return false;
return true;
}
bool CmdResult::validateR6Response()
{
if(error!=Ok) return validateError();
if((response & 0xe008)==0) return true;
DBGERR("CMD%d: R6 response error(s):\n",cmd);
if(response & (1<<15)) DBGERR("command CRC failed\n");
if(response & (1<<14)) DBGERR("illegal command\n");
if(response & (1<<13)) DBGERR("unknown error\n");
if(response & (1<<3)) DBGERR("AKE_SEQ error\n");
return false;
}
unsigned char CmdResult::getState()
{
unsigned char result=(response>>9) & 0xf;
DBG("CMD%d: State: ",cmd);
switch(result)
{
case 0: DBG("Idle\n"); break;
case 1: DBG("Ready\n"); break;
case 2: DBG("Ident\n"); break;
case 3: DBG("Stby\n"); break;
case 4: DBG("Tran\n"); break;
case 5: DBG("Data\n"); break;
case 6: DBG("Rcv\n"); break;
case 7: DBG("Prg\n"); break;
case 8: DBG("Dis\n"); break;
case 9: DBG("Btst\n"); break;
default: DBG("Unknown\n"); break;
}
return result;
}
//
// Class Command
//
/**
* \internal
* This class allows sending commands to an SD or MMC
*/
class Command
{
public:
/**
* \internal
* SD/MMC commands
* - bit #7 is @ 1 if a command is an ACMDxx. send() will send the
* sequence CMD55, CMDxx
* - bit from #0 to #5 indicate command index (CMD0..CMD63)
* - bit #6 is don't care
*/
enum CommandType
{
CMD0=0, //GO_IDLE_STATE
CMD2=2, //ALL_SEND_CID
CMD3=3, //SEND_RELATIVE_ADDR
ACMD6=0x80 | 6, //SET_BUS_WIDTH
CMD7=7, //SELECT_DESELECT_CARD
ACMD41=0x80 | 41, //SEND_OP_COND (SD)
CMD8=8, //SEND_IF_COND
CMD9=9, //SEND_CSD
CMD12=12, //STOP_TRANSMISSION
CMD13=13, //SEND_STATUS
CMD16=16, //SET_BLOCKLEN
CMD17=17, //READ_SINGLE_BLOCK
CMD18=18, //READ_MULTIPLE_BLOCK
ACMD23=0x80 | 23, //SET_WR_BLK_ERASE_COUNT (SD)
CMD24=24, //WRITE_BLOCK
CMD25=25, //WRITE_MULTIPLE_BLOCK
CMD55=55 //APP_CMD
};
/**
* \internal
* Send a command.
* \param cmd command index (CMD0..CMD63) or ACMDxx command
* \param arg the 32 bit argument to the command
* \return a CmdResult object
*/
static CmdResult send(CommandType cmd, unsigned int arg)
{
if(static_cast<unsigned char>(cmd) & 0x80)
{
DBG("ACMD%d\n",static_cast<unsigned char>(cmd) & 0x3f);
} else {
DBG("CMD%d\n",static_cast<unsigned char>(cmd) & 0x3f);
}
return IRQsend(cmd,arg);
}
/**
* \internal
* Send a command. Can be called with interrupts disabled as it does not
* print any debug information.
* \param cmd command index (CMD0..CMD63) or ACMDxx command
* \param arg the 32 bit argument to the command
* \return a CmdResult object
*/
static CmdResult IRQsend(CommandType cmd, unsigned int arg);
/**
* \internal
* Set the relative card address, obtained during initialization.
* \param r the card's rca
*/
static void setRca(unsigned short r) { rca=r; }
/**
* \internal
* \return the card's rca, as set by setRca
*/
static unsigned int getRca() { return static_cast<unsigned int>(rca); }
private:
static unsigned short rca;///<\internal Card's relative address
};
CmdResult Command::IRQsend(CommandType cmd, unsigned int arg)
{
unsigned char cc=static_cast<unsigned char>(cmd);
//Handle ACMDxx as CMD55, CMDxx
if(cc & 0x80)
{
CmdResult r=IRQsend(CMD55,(static_cast<unsigned int>(rca))<<16);
if(r.IRQvalidateR1Response()==false)
return CmdResult(cc & 0x3f,CmdResult::ACMDFail);
//Bit 5 @ 1 = next command will be interpreted as ACMD
if((r.getResponse() & (1<<5))==0)
return CmdResult(cc & 0x3f,CmdResult::ACMDFail);
}
//Send command
cc &= 0x3f;
unsigned int command=SDIO_CMD_CPSMEN | static_cast<unsigned int>(cc);
if(cc!=CMD0) command |= SDIO_CMD_WAITRESP_0; //CMD0 has no response
if(cc==CMD2) command |= SDIO_CMD_WAITRESP_1; //CMD2 has long response
if(cc==CMD9) command |= SDIO_CMD_WAITRESP_1; //CMD9 has long response
SDIO->ARG=arg;
SDIO->CMD=command;
//CMD0 has no response, so wait until it is sent
if(cc==CMD0)
{
for(int i=0;i<500;i++)
{
if(SDIO->STA & SDIO_STA_CMDSENT)
{
SDIO->ICR=0x7ff;//Clear flags
return CmdResult(cc,CmdResult::Ok);
}
delayUs(1);
}
SDIO->ICR=0x7ff;//Clear flags
return CmdResult(cc,CmdResult::Timeout);
}
//Command is not CMD0, so wait a reply
for(int i=0;i<500;i++)
{
unsigned int status=SDIO->STA;
if(status & SDIO_STA_CMDREND)
{
SDIO->ICR=0x7ff;//Clear flags
if(SDIO->RESPCMD==cc) return CmdResult(cc,CmdResult::Ok);
else return CmdResult(cc,CmdResult::RespNotMatch);
}
if(status & SDIO_STA_CCRCFAIL)
{
SDIO->ICR=SDIO_ICR_CCRCFAILC;
return CmdResult(cc,CmdResult::CRCFail);
}
if(status & SDIO_STA_CTIMEOUT) break;
delayUs(1);
}
SDIO->ICR=SDIO_ICR_CTIMEOUTC;
return CmdResult(cc,CmdResult::Timeout);
}
unsigned short Command::rca=0;
//
// Class DataResult
//
/**
* \internal
* Contains the result of sending/receiving a data block
*/
class DataResult
{
public:
/**
* \internal
* Possible outcomes of sending or receiving data
*/
enum Error
{
Ok=0,
Timeout,
CRCFail,
RXOverrun,
TXUnderrun,
StartBitFail
};
/**
* \internal
* Default constructor
*/
DataResult(): error(Ok) {}
/**
* \internal
* Constructor, set the result.
* \param error error type
*/
DataResult(Error error): error(error) {}
/**
* \internal
* \return the error flags
*/
Error getError() { return error; }
/**
* \internal
* Checks if errors occurred while sending/receiving data.
* \return true if no errors, false otherwise
*/
bool validateError();
private:
Error error;
};
bool DataResult::validateError()
{
switch(error)
{
case Ok:
return true;
case Timeout:
DBGERR("Data Timeout\n");
break;
case CRCFail:
DBGERR("Data CRC Fail\n");
break;
case RXOverrun:
DBGERR("Data overrun\n");
break;
case TXUnderrun:
DBGERR("Data underrun\n");
break;
case StartBitFail:
DBGERR("Data start bit Fail\n");
break;
}
return false;
}
//
// Class ClockController
//
/**
* \internal
* This class controls the clock speed of the SDIO peripheral. The SDIO
* peripheral, when used in polled mode, requires two timing critical pieces of
* code: the one to send and the one to receive a data block. This because
* the peripheral has a 128 byte fifo while the block size is 512 byte, and
* if fifo underrun/overrun occurs the peripheral does not pause communcation,
* instead it simply aborts the data transfer. Since the speed of the code to
* read/write a data block depends on too many factors, such as compiler
* optimizations, code running from internal flash or external ram, and the
* cpu clock speed, a dynamic clocking approach was chosen.
*/
class ClockController
{
public:
/**
* \internal. Set a low clock speed of 400KHz or less, used for
* detecting SD/MMC cards. This function as a side effect enables 1bit bus
* width, and disables clock powersave, since it is not allowed by SD spec.
*/
static void setLowSpeedClock()
{
clockReductionAvailable=0;
// No hardware flow control, SDIO_CK generated on rising edge, 1bit bus
// width, no clock bypass, no powersave.
// Set low clock speed 400KHz, 72MHz/400KHz-2=178
SDIO->CLKCR=CLOCK_400KHz | SDIO_CLKCR_CLKEN;
SDIO->DTIMER=240000; //Timeout 600ms expressed in SD_CK cycles
}
/**
* \internal
* Automatically select the data speed.
* Since the maximum speed depends on many factors, such as code running in
* internal or external RAM, compiler optimizations etc. this routine
* selects the highest sustainable data transfer speed.
* This is done by binary search until the highest clock speed that causes
* no errors is found.
* This function as a side effect enables 4bit bus width, and clock
* powersave.
*/
static void calibrateClockSpeed(SDIODriver *sdio);
/**
* \internal
* Since clock speed is set dynamically by bynary search at runtime, a
* corner case might be that of a clock speed which results in unreliable
* data transfer, that sometimes succeeds, and sometimes fail.
* For maximum robustness, this function is provided to reduce the clock
* speed slightly in case a data transfer should fail after clock
* calibration. To avoid inadvertently considering other kind of issues as
* clock issues, this function can be called only MAX_ALLOWED_REDUCTIONS
* times after clock calibration, subsequent calls will fail. This will
* avoid other issues causing an ever decreasing clock speed.
* \return true on success, false on failure
*/
static bool reduceClockSpeed() { return IRQreduceClockSpeed(); }
/**
* \internal
* Same as reduceClockSpeed(), can be called with interrupts disabled.
* \return true on success, false on failure
*/
static bool IRQreduceClockSpeed();
/**
* \internal
* Read and write operation do retry during normal use for robustness, but
* during clock claibration they must not retry for speed reasons. This
* member function returns 1 during clock claibration and MAX_RETRY during
* normal use.
*/
static unsigned char getRetryCount() { return retries; }
private:
/**
* Set SDIO clock speed
* \param clkdiv speed is SDIOCLK/(clkdiv+2)
*/
static void setClockSpeed(unsigned int clkdiv);
/**
* \internal
* Value of SDIO->CLKCR that will give a 400KHz clock, depending on cpu
* clock speed.
*/
#ifdef SYSCLK_FREQ_72MHz
static const unsigned int CLOCK_400KHz=178;
#elif SYSCLK_FREQ_56MHz
static const unsigned int CLOCK_400KHz=138;
#elif SYSCLK_FREQ_48MHz
static const unsigned int CLOCK_400KHz=118;
#elif SYSCLK_FREQ_36MHz
static const unsigned int CLOCK_400KHz=88;
#elif SYSCLK_FREQ_24MHz
static const unsigned int CLOCK_400KHz=58;
#else
static const unsigned int CLOCK_400KHz=18;
#endif
///\internal Clock enabled, bus width 4bit, clock powersave enabled.
static const unsigned int CLKCR_FLAGS=SDIO_CLKCR_CLKEN |
SDIO_CLKCR_WIDBUS_0 | SDIO_CLKCR_PWRSAV;
///\internal Maximum number of calls to IRQreduceClockSpeed() allowed
///When using polled mode this is a critical parameter, if SDIO driver
///starts to fail, it might be a good idea to increase this
static const unsigned char MAX_ALLOWED_REDUCTIONS=7;
///\internal value returned by getRetryCount() while *not* calibrating clock.
///When using polled mode this is a critical parameter, if SDIO driver
///starts to fail, it might be a good idea to increase this
static const unsigned char MAX_RETRY=10;
///\internal Used to allow only one call to reduceClockSpeed()
static unsigned char clockReductionAvailable;
static unsigned char retries;
};
void ClockController::calibrateClockSpeed(SDIODriver *sdio)
{
//During calibration we call readBlock which will call reduceClockSpeed()
//so not to invalidate calibration clock reduction must not be available
clockReductionAvailable=0;
retries=1;
DBG("Automatic speed calibration\n");
unsigned int buffer[512/sizeof(unsigned int)];
unsigned int minFreq=CLOCK_400KHz; //400KHz, independent of CPU clock
unsigned int maxFreq=1; //24MHz with CPU running @ 72MHz
unsigned int selected;
while(minFreq-maxFreq>1)
{
selected=(minFreq+maxFreq)/2;
DBG("Trying CLKCR=%d\n",selected);
setClockSpeed(selected);
if(sdio->readBlock(reinterpret_cast<unsigned char*>(buffer),512,0)==512)
minFreq=selected;
else maxFreq=selected;
}
//Last round of algorithm
setClockSpeed(maxFreq);
if(sdio->readBlock(reinterpret_cast<unsigned char*>(buffer),512,0)==512)
{
DBG("Optimal CLKCR=%d\n",maxFreq);
} else {
setClockSpeed(minFreq);
DBG("Optimal CLKCR=%d\n",minFreq);
}
//Make clock reduction available
clockReductionAvailable=MAX_ALLOWED_REDUCTIONS;
retries=MAX_RETRY;
}
bool ClockController::IRQreduceClockSpeed()
{
//Ensure this function can be called only twice per calibration
if(clockReductionAvailable==0) return false;
clockReductionAvailable--;
unsigned int currentClkcr=SDIO->CLKCR & 0xff;
if(currentClkcr==CLOCK_400KHz) return false; //No lower than this value
//If the value of clockcr is low, increasing it by one is enough since
//frequency changes a lot, otherwise increase by 2.
if(currentClkcr<10) currentClkcr++;
else currentClkcr+=2;
setClockSpeed(currentClkcr);
return true;
}
void ClockController::setClockSpeed(unsigned int clkdiv)
{
SDIO->CLKCR=clkdiv | CLKCR_FLAGS;
//Timeout 600ms expressed in SD_CK cycles
SDIO->DTIMER=(6*SystemCoreClock)/((clkdiv+2)*10);
}
unsigned char ClockController::clockReductionAvailable=false;
unsigned char ClockController::retries=ClockController::MAX_RETRY;
//
// Data send/receive functions
//
/**
* \internal
* Wait until the card is ready for data transfer.
* Can be called independently of the card being selected.
* \return true on success, false on failure
*/
static bool waitForCardReady()
{
const int timeout=1500; //Timeout 1.5 second
const int sleepTime=2;
for(int i=0;i<timeout/sleepTime;i++)
{
CmdResult cr=Command::send(Command::CMD13,Command::getRca()<<16);
if(cr.validateR1Response()==false) return false;
//Bit 8 in R1 response means ready for data.
if(cr.getResponse() & (1<<8)) return true;
Thread::sleep(sleepTime);
}
DBGERR("Timeout waiting card ready\n");
return false;
}
#ifdef __ENABLE_XRAM
/**
* \internal
* Receive a data block. The end of the data block must be told to the SDIO
* peripheral in SDIO->DLEN and must match the size parameter given to this
* function.
* \param buffer buffer where to store received data. Its size must be >=size
* \param buffer size, which *must* be multiple of 8 words (32bytes)
* Note that the size parameter must be expressed in word (4bytes), while
* the value in SDIO->DLEN is expressed in bytes.
* \return a DataResult object
*/
static DataResult IRQreceiveDataBlock(unsigned int *buffer, unsigned int size)
{
// A note on speed.
// Due to the auto calibration of SDIO clock speed being done with
// IRQreceiveDataBlock(), the speed of this function must be comparable
// with the speed of IRQsendDataBlock(), otherwise IRQsendDataBlock()
// will fail because of data underrun.
const unsigned int *bufend=buffer+size;
unsigned int status;
for(;;)
{
status=SDIO->STA;
if(status & (SDIO_STA_RXOVERR | SDIO_STA_DCRCFAIL |
SDIO_STA_DTIMEOUT | SDIO_STA_STBITERR | SDIO_STA_DBCKEND)) break;
if((status & SDIO_STA_RXFIFOHF) && (buffer!=bufend))
{
//Read 8 words from the fifo, loop entirely unrolled for speed
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
*buffer=SDIO->FIFO; buffer++;
}
}
SDIO->ICR=0x7ff;//Clear flags
if(status & SDIO_STA_RXOVERR) return DataResult(DataResult::RXOverrun);
if(status & SDIO_STA_DCRCFAIL) return DataResult(DataResult::CRCFail);
if(status & SDIO_STA_DTIMEOUT) return DataResult(DataResult::Timeout);
if(status & SDIO_STA_STBITERR) return DataResult(DataResult::StartBitFail);
//Read eventual data left in the FIFO
for(;;)
{
if((SDIO->STA & SDIO_STA_RXDAVL)==0) break;
*buffer=SDIO->FIFO; buffer++;
}
return DataResult(DataResult::Ok);
}
/**
* \internal
* Send a data block. The end of the data block must be told to the SDIO
* peripheral in SDIO->DLEN and must match the size parameter given to this
* function.
* \param buffer buffer where to store received data. Its size must be >=size
* \param buffer size, which *must* be multiple of 8 words (32bytes).
* Note that the size parameter must be expressed in word (4bytes), while
* the value in SDIO->DLEN is expressed in bytes.
* \return a DataResult object
*/
static DataResult IRQsendDataBlock(const unsigned int *buffer, unsigned int size)
{
// A note on speed.
// Due to the auto calibration of SDIO clock speed being done with
// IRQreceiveDataBlock(), the speed of this function must be comparable
// with the speed of IRQreceiveDataBlock(), otherwise this function
// will fail because of data underrun.
const unsigned int *bufend=buffer+size;
unsigned int status;
for(;;)
{
status=SDIO->STA;
if(status & (SDIO_STA_TXUNDERR | SDIO_STA_DCRCFAIL |
SDIO_STA_DTIMEOUT | SDIO_STA_STBITERR | SDIO_STA_DBCKEND)) break;
if((status & SDIO_STA_TXFIFOHE) && (buffer!=bufend))
{
//Write 8 words to the fifo, loop entirely unrolled for speed
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
SDIO->FIFO=*buffer; buffer++;
}
}
SDIO->ICR=0x7ff;//Clear flags
if(status & SDIO_STA_TXUNDERR) return DataResult(DataResult::TXUnderrun);
if(status & SDIO_STA_DCRCFAIL) return DataResult(DataResult::CRCFail);
if(status & SDIO_STA_DTIMEOUT) return DataResult(DataResult::Timeout);
if(status & SDIO_STA_STBITERR) return DataResult(DataResult::StartBitFail);
return DataResult(DataResult::Ok);
}
/**
* \internal
* Read a single block of 512 bytes from an SD/MMC card.
* Card must be selected prior to caling this function.
* \param buffer, a buffer whose size is >=512 bytes, word aligned
* \param lba logical block address of the block to read.
*/
static bool singleBlockRead(unsigned int *buffer, unsigned int lba)
{
if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC
if(waitForCardReady()==false) return false;
CmdResult cr;
DataResult dr;
bool failed=true;
for(;;)
{
// Since we read with polling, a context switch or interrupt here
// would cause a fifo overrun, so we disable interrupts.
FastInterruptDisableLock dLock;
SDIO->DLEN=512;
//Block size 512 bytes, block data xfer, from card to controller
SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN;
cr=Command::IRQsend(Command::CMD17,lba);
if(cr.IRQvalidateR1Response())
{
dr=IRQreceiveDataBlock(buffer,512/sizeof(unsigned int));
SDIO->DCTRL=0; //Disable data path state machine
//If failed because too slow check if it is possible to reduce speed
if(dr.getError()==DataResult::RXOverrun)
{
if(ClockController::IRQreduceClockSpeed())
{
//Disabling interrupts for too long is bad
FastInterruptEnableLock eLock(dLock);
//After an error during data xfer the card might be a little
//confused. So send STOP_TRANSMISSION command to reassure it
cr=Command::send(Command::CMD12,0);
if(cr.validateR1Response()) continue;
}
}
if(dr.getError()==DataResult::Ok) failed=false;
}
break;
}
if(failed)
{
cr.validateR1Response();
dr.validateError();
//After an error during data xfer the card might be a little
//confused. So send STOP_TRANSMISSION command to reassure it
cr=Command::send(Command::CMD12,0);
cr.validateR1Response();
return false;
}
return true;
}
/**
* \internal
* Write a single block of 512 bytes to an SD/MMC card
* Card must be selected prior to caling this function.
* \param buffer, a buffer whose size is >=512 bytes
* \param lba logical block address of the block to write.
*/
static bool singleBlockWrite(const unsigned int *buffer, unsigned int lba)
{
if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC
if(waitForCardReady()==false) return false;
bool failed=true;
CmdResult cr;
DataResult dr;
for(;;)
{
// Since we write with polling, a context switch or interrupt here
// would cause a fifo overrun, so we disable interrupts.
FastInterruptDisableLock dLock;
cr=Command::IRQsend(Command::CMD24,lba);
if(cr.IRQvalidateR1Response())
{
SDIO->DLEN=512;
//Block size 512 bytes, block data xfer, from controller to card
SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DTEN;
dr=IRQsendDataBlock(buffer,512/sizeof(unsigned int));
SDIO->DCTRL=0; //Disable data path state machine
//If failed because too slow check if it is possible to reduce speed
if(dr.getError()==DataResult::TXUnderrun)
{
if(ClockController::IRQreduceClockSpeed())
{
//Disabling interrupts for too long is bad
FastInterruptEnableLock eLock(dLock);
//After an error during data xfer the card might be a little
//confused. So send STOP_TRANSMISSION command to reassure it
cr=Command::send(Command::CMD12,0);
if(cr.validateR1Response()) continue;
}
}
if(dr.getError()==DataResult::Ok) failed=false;
}
break;
}
if(failed)
{
cr.validateR1Response();
dr.validateError();
//After an error during data xfer the card might be a little
//confused. So send STOP_TRANSMISSION command to reassure it
cr=Command::send(Command::CMD12,0);
cr.validateR1Response();
return false;
}
return true;
}
#else //__ENABLE_XRAM
/**
* \internal
* Prints the errors that may occur during a DMA transfer
*/
static void displayBlockTransferError()
{
DBGERR("Block transfer error\n");
if(dmaFlags & DMA_ISR_TEIF4) DBGERR("* DMA Transfer error\n");
if(sdioFlags & SDIO_STA_STBITERR) DBGERR("* SDIO Start bit error\n");
if(sdioFlags & SDIO_STA_RXOVERR) DBGERR("* SDIO RX Overrun\n");
if(sdioFlags & SDIO_STA_TXUNDERR) DBGERR("* SDIO TX Underrun error\n");
if(sdioFlags & SDIO_STA_DCRCFAIL) DBGERR("* SDIO Data CRC fail\n");
if(sdioFlags & SDIO_STA_DTIMEOUT) DBGERR("* SDIO Data timeout\n");
}
/**
* \internal
* Read a given number of contiguous 512 byte blocks from an SD/MMC card.
* Card must be selected prior to calling this function.
* \param buffer, a buffer whose size is 512*nblk bytes, word aligned
* \param nblk number of blocks to read.
* \param lba logical block address of the first block to read.
*/
static bool multipleBlockRead(unsigned int *buffer, unsigned int nblk,
unsigned int lba)
{
if(nblk==0) return true;
while(nblk>511)
{
if(multipleBlockRead(buffer,511,lba)==false) return false;
buffer+=511*512;
nblk-=511;
lba+=511;
}
if(waitForCardReady()==false) return false;
if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC
//Clear both SDIO and DMA interrupt flags
SDIO->ICR=0x7ff;
DMA2->IFCR=DMA_IFCR_CGIF4;
transferError=false;
dmaFlags=sdioFlags=0;
waiting=Thread::getCurrentThread();
//Data transfer is considered complete once the DMA transfer complete
//interrupt occurs, that happens when the last data was written in the
//buffer. Both SDIO and DMA error interrupts are active to catch errors
SDIO->MASK=SDIO_MASK_STBITERRIE | //Interrupt on start bit error
SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun
SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun
SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail
SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout
DMA2_Channel4->CPAR=reinterpret_cast<unsigned int>(&SDIO->FIFO);
DMA2_Channel4->CMAR=reinterpret_cast<unsigned int>(buffer);
DMA2_Channel4->CNDTR=nblk*512/sizeof(unsigned int);
DMA2_Channel4->CCR=DMA_CCR4_PL_1 | //High priority DMA stream
DMA_CCR4_MSIZE_1 | //Write 32bit at a time to RAM
DMA_CCR4_PSIZE_1 | //Read 32bit at a time from SDIO
DMA_CCR4_MINC | //Increment RAM pointer
0 | //Peripheral to memory direction
DMA_CCR4_TCIE | //Interrupt on transfer complete
DMA_CCR4_TEIE | //Interrupt on transfer error
DMA_CCR4_EN; //Start the DMA
SDIO->DLEN=nblk*512;
if(waiting==0)
{
DBGERR("Premature wakeup\n");
transferError=true;
}
CmdResult cr=Command::send(nblk>1 ? Command::CMD18 : Command::CMD17,lba);
if(cr.validateR1Response())
{
//Block size 512 bytes, block data xfer, from card to controller
SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN;
FastInterruptDisableLock dLock;
while(waiting)
{
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
}
} else transferError=true;
DMA2_Channel4->CCR=0;
while(DMA2_Channel4->CCR & DMA_CCR4_EN) ; //DMA may take time to stop
SDIO->DCTRL=0; //Disable data path state machine
SDIO->MASK=0;
// CMD12 is sent to end CMD18 (multiple block read), or to abort an
// unfinished read in case of errors
if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0);
if(transferError || cr.validateR1Response()==false)
{
displayBlockTransferError();
ClockController::reduceClockSpeed();
return false;
}
return true;
}
/**
* \internal
* Write a given number of contiguous 512 byte blocks to an SD/MMC card.
* Card must be selected prior to calling this function.
* \param buffer, a buffer whose size is 512*nblk bytes, word aligned
* \param nblk number of blocks to write.
* \param lba logical block address of the first block to write.
*/
static bool multipleBlockWrite(const unsigned int *buffer, unsigned int nblk,
unsigned int lba)
{
if(nblk==0) return true;
while(nblk>511)
{
if(multipleBlockWrite(buffer,511,lba)==false) return false;
buffer+=511*512;
nblk-=511;
lba+=511;
}
if(waitForCardReady()==false) return false;
if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC
if(nblk>1)
{
CmdResult cr=Command::send(Command::ACMD23,nblk);
if(cr.validateR1Response()==false) return false;
}
//Clear both SDIO and DMA interrupt flags
SDIO->ICR=0x7ff;
DMA2->IFCR=DMA_IFCR_CGIF4;
transferError=false;
dmaFlags=sdioFlags=0;
waiting=Thread::getCurrentThread();
//Data transfer is considered complete once the SDIO transfer complete
//interrupt occurs, that happens when the last data was written to the SDIO
//Both SDIO and DMA error interrupts are active to catch errors
SDIO->MASK=SDIO_MASK_DATAENDIE | //Interrupt on data end
SDIO_MASK_STBITERRIE | //Interrupt on start bit error
SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun
SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun
SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail
SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout
DMA2_Channel4->CPAR=reinterpret_cast<unsigned int>(&SDIO->FIFO);
DMA2_Channel4->CMAR=reinterpret_cast<unsigned int>(buffer);
DMA2_Channel4->CNDTR=nblk*512/sizeof(unsigned int);
DMA2_Channel4->CCR=DMA_CCR4_PL_1 | //High priority DMA stream
DMA_CCR4_MSIZE_1 | //Read 32bit at a time from RAM
DMA_CCR4_PSIZE_1 | //Write 32bit at a time to SDIO
DMA_CCR4_MINC | //Increment RAM pointer
DMA_CCR4_DIR | //Memory to peripheral direction
DMA_CCR4_TEIE | //Interrupt on transfer error
DMA_CCR4_EN; //Start the DMA
SDIO->DLEN=nblk*512;
if(waiting==0)
{
DBGERR("Premature wakeup\n");
transferError=true;
}
CmdResult cr=Command::send(nblk>1 ? Command::CMD25 : Command::CMD24,lba);
if(cr.validateR1Response())
{
//Block size 512 bytes, block data xfer, from card to controller
SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTEN;
FastInterruptDisableLock dLock;
while(waiting)
{
Thread::IRQwait();
{
FastInterruptEnableLock eLock(dLock);
Thread::yield();
}
}
} else transferError=true;
DMA2_Channel4->CCR=0;
while(DMA2_Channel4->CCR & DMA_CCR4_EN) ; //DMA may take time to stop
SDIO->DCTRL=0; //Disable data path state machine
SDIO->MASK=0;
// CMD12 is sent to end CMD25 (multiple block write), or to abort an
// unfinished write in case of errors
if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0);
if(transferError || cr.validateR1Response()==false)
{
displayBlockTransferError();
ClockController::reduceClockSpeed();
return false;
}
return true;
}
#endif //__ENABLE_XRAM
//
// Class CardSelector
//
/**
* \internal
* Simple RAII class for selecting an SD/MMC card an automatically deselect it
* at the end of the scope.
*/
class CardSelector
{
public:
/**
* \internal
* Constructor. Selects the card.
* The result of the select operation is available through its succeded()
* member function
*/
explicit CardSelector()
{
success=Command::send(
Command::CMD7,Command::getRca()<<16).validateR1Response();
}
/**
* \internal
* \return true if the card was selected, false on error
*/
bool succeded() { return success; }
/**
* \internal
* Destructor, ensures that the card is deselected
*/
~CardSelector()
{
Command::send(Command::CMD7,0); //Deselect card. This will timeout
}
private:
bool success;
};
//
// Initialization helper functions
//
/**
* \internal
* Initialzes the SDIO peripheral in the STM32
*/
static void initSDIOPeripheral()
{
{
//Doing read-modify-write on RCC->APBENR2 and gpios, better be safe
FastInterruptDisableLock lock;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN;
RCC_SYNC();
#ifdef __ENABLE_XRAM
RCC->AHBENR |= RCC_AHBENR_SDIOEN;
#else //__ENABLE_XRAM
RCC->AHBENR |= RCC_AHBENR_SDIOEN | RCC_AHBENR_DMA2EN;
#endif //__ENABLE_XRAM
RCC_SYNC();
sdD0::mode(Mode::ALTERNATE);
sdD1::mode(Mode::ALTERNATE);
sdD2::mode(Mode::ALTERNATE);
sdD3::mode(Mode::ALTERNATE);
sdCLK::mode(Mode::ALTERNATE);
sdCMD::mode(Mode::ALTERNATE);
}
#ifndef __ENABLE_XRAM
NVIC_SetPriority(DMA2_Channel4_5_IRQn,15);//Low priority for DMA
NVIC_EnableIRQ(DMA2_Channel4_5_IRQn);
NVIC_SetPriority(SDIO_IRQn,15);//Low priority for SDIO
NVIC_EnableIRQ(SDIO_IRQn);
#endif //__ENABLE_XRAM
SDIO->POWER=0; //Power off state
delayUs(1);
SDIO->CLKCR=0;
SDIO->CMD=0;
SDIO->DCTRL=0;
SDIO->ICR=0xc007ff;
SDIO->POWER=SDIO_POWER_PWRCTRL_1 | SDIO_POWER_PWRCTRL_0; //Power on state
//This delay is particularly important: when setting the POWER register a
//glitch on the CMD pin happens. This glitch has a fast fall time and a slow
//rise time resembling an RC charge with a ~6us rise time. If the clock is
//started too soon, the card sees a clock pulse while CMD is low, and
//interprets it as a start bit. No, setting POWER to powerup does not
//eliminate the glitch.
delayUs(10);
ClockController::setLowSpeedClock();
}
/**
* \internal
* Detect if the card is an SDHC, SDv2, SDv1, MMC
* \return Type of card: (1<<0)=MMC (1<<1)=SDv1 (1<<2)=SDv2 (1<<2)|(1<<3)=SDHC
* or Invalid if card detect failed.
*/
static CardType detectCardType()
{
const int INIT_TIMEOUT=200; //200*10ms= 2 seconds
CmdResult r=Command::send(Command::CMD8,0x1aa);
if(r.validateError())
{
//We have an SDv2 card connected
if(r.getResponse()!=0x1aa)
{
DBGERR("CMD8 validation: voltage range fail\n");
return Invalid;
}
for(int i=0;i<INIT_TIMEOUT;i++)
{
//Bit 30 @ 1 = tell the card we like SDHCs
r=Command::send(Command::ACMD41,(1<<30) | sdVoltageMask);
//ACMD41 sends R3 as response, whose CRC is wrong.
if(r.getError()!=CmdResult::Ok && r.getError()!=CmdResult::CRCFail)
{
r.validateError();
return Invalid;
}
if((r.getResponse() & (1<<31))==0) //Busy bit
{
Thread::sleep(10);
continue;
}
if((r.getResponse() & sdVoltageMask)==0)
{
DBGERR("ACMD41 validation: voltage range fail\n");
return Invalid;
}
DBG("ACMD41 validation: looped %d times\n",i);
if(r.getResponse() & (1<<30))
{
DBG("SDHC\n");
return SDHC;
} else {
DBG("SDv2\n");
return SDv2;
}
}
DBGERR("ACMD41 validation: looped until timeout\n");
return Invalid;
} else {
//We have an SDv1 or MMC
r=Command::send(Command::ACMD41,sdVoltageMask);
//ACMD41 sends R3 as response, whose CRC is wrong.
if(r.getError()!=CmdResult::Ok && r.getError()!=CmdResult::CRCFail)
{
//MMC card
DBG("MMC card\n");
return MMC;
} else {
//SDv1 card
for(int i=0;i<INIT_TIMEOUT;i++)
{
//ACMD41 sends R3 as response, whose CRC is wrong.
if(r.getError()!=CmdResult::Ok &&
r.getError()!=CmdResult::CRCFail)
{
r.validateError();
return Invalid;
}
if((r.getResponse() & (1<<31))==0) //Busy bit
{
Thread::sleep(10);
//Send again command
r=Command::send(Command::ACMD41,sdVoltageMask);
continue;
}
if((r.getResponse() & sdVoltageMask)==0)
{
DBGERR("ACMD41 validation: voltage range fail\n");
return Invalid;
}
DBG("ACMD41 validation: looped %d times\nSDv1\n",i);
return SDv1;
}
DBGERR("ACMD41 validation: looped until timeout\n");
return Invalid;
}
}
}
//
// class SDIODriver
//
intrusive_ref_ptr<SDIODriver> SDIODriver::instance()
{
static FastMutex m;
static intrusive_ref_ptr<SDIODriver> instance;
Lock<FastMutex> l(m);
if(!instance) instance=new SDIODriver();
return instance;
}
ssize_t SDIODriver::readBlock(void* buffer, size_t size, off_t where)
{
if(where % 512 || size % 512) return -EFAULT;
unsigned int lba=where/512;
unsigned int nSectors=size/512;
Lock<FastMutex> l(mutex);
DBG("SDIODriver::readBlock(): nSectors=%d\n",nSectors);
bool aligned=BufferConverter::isWordAligned(buffer);
if(aligned==false) DBG("Buffer misaligned\n");
for(int i=0;i<ClockController::getRetryCount();i++)
{
//Select card
CardSelector selector;
if(selector.succeded()==false) continue;
bool error=false;
#ifdef __ENABLE_XRAM
// In the XRAM fallback code multiple sector read is implemented as
// a sequence of single block read operations
unsigned char *tempBuffer=reinterpret_cast<unsigned char*>(buffer);
unsigned int tempLba=lba;
for(unsigned int j=0;j<nSectors;j++)
{
unsigned int* b=BufferConverter::toWordAlignedWithoutCopy(tempBuffer);
if(singleBlockRead(b,tempLba)==false)
{
error=true;
break;
}
BufferConverter::toOriginalBuffer();
tempBuffer+=512;
tempLba++;
}
#else //__ENABLE_XRAM
// If XRAM is not enabled, then check pointer alignment, and if it is
// aligned use a single multipleBlockRead(), else use the buffer
// converter and read a sector at a time
if(aligned)
{
if(multipleBlockRead(reinterpret_cast<unsigned int*>(buffer),
nSectors,lba)==false) error=true;
} else {
unsigned char *tempBuffer=reinterpret_cast<unsigned char*>(buffer);
unsigned int tempLba=lba;
for(unsigned int j=0;j<nSectors;j++)
{
unsigned int* b=BufferConverter::toWordAlignedWithoutCopy(tempBuffer);
if(multipleBlockRead(b,1,tempLba)==false)
{
error=true;
break;
}
BufferConverter::toOriginalBuffer();
tempBuffer+=512;
tempLba++;
}
}
#endif //__ENABLE_XRAM
if(error==false)
{
if(i>0) DBGERR("Read: required %d retries\n",i);
return size;
}
}
return -EBADF;
}
ssize_t SDIODriver::writeBlock(const void* buffer, size_t size, off_t where)
{
if(where % 512 || size % 512) return -EFAULT;
unsigned int lba=where/512;
unsigned int nSectors=size/512;
Lock<FastMutex> l(mutex);
DBG("SDIODriver::writeBlock(): nSectors=%d\n",nSectors);
bool aligned=BufferConverter::isWordAligned(buffer);
if(aligned==false) DBG("Buffer misaligned\n");
for(int i=0;i<ClockController::getRetryCount();i++)
{
//Select card
CardSelector selector;
if(selector.succeded()==false) continue;
bool error=false;
#ifdef __ENABLE_XRAM
// In the XRAM fallback code multiple sector write is implemented as
// a sequence of single block write operations
const unsigned char *tempBuffer=
reinterpret_cast<const unsigned char*>(buffer);
unsigned int tempLba=lba;
for(unsigned int j=0;j<nSectors;j++)
{
const unsigned int* b=BufferConverter::toWordAligned(tempBuffer);
if(singleBlockWrite(b,tempLba)==false)
{
error=true;
break;
}
tempBuffer+=512;
tempLba++;
}
#else //__ENABLE_XRAM
// If XRAM is not enabled, then check pointer alignment, and if it is
// aligned use a single multipleBlockWrite(), else use the buffer
// converter and write a sector at a time
if(aligned)
{
if(multipleBlockWrite(reinterpret_cast<const unsigned int*>(buffer),
nSectors,lba)==false) error=true;
} else {
const unsigned char *tempBuffer=
reinterpret_cast<const unsigned char*>(buffer);
unsigned int tempLba=lba;
for(unsigned int j=0;j<nSectors;j++)
{
const unsigned int* b=BufferConverter::toWordAligned(tempBuffer);
if(multipleBlockWrite(b,1,tempLba)==false)
{
error=true;
break;
}
tempBuffer+=512;
tempLba++;
}
}
#endif //__ENABLE_XRAM
if(error==false)
{
if(i>0) DBGERR("Write: required %d retries\n",i);
return size;
}
}
return -EBADF;
}
int SDIODriver::ioctl(int cmd, void* arg)
{
DBG("SDIODriver::ioctl()\n");
if(cmd!=IOCTL_SYNC) return -ENOTTY;
Lock<FastMutex> l(mutex);
//Note: no need to select card, since status can be queried even with card
//not selected.
return waitForCardReady() ? 0 : -EFAULT;
}
SDIODriver::SDIODriver() : Device(Device::BLOCK)
{
initSDIOPeripheral();
// This is more important than it seems, since CMD55 requires the card's RCA
// as argument. During initalization, after CMD0 the card has an RCA of zero
// so without this line ACMD41 will fail and the card won't be initialized.
Command::setRca(0);
//Send card reset command
CmdResult r=Command::send(Command::CMD0,0);
if(r.validateError()==false) return;
cardType=detectCardType();
if(cardType==Invalid) return; //Card detect failed
if(cardType==MMC) return; //MMC cards currently unsupported
// Now give an RCA to the card. In theory we should loop and enumerate all
// the cards but this driver supports only one card.
r=Command::send(Command::CMD2,0);
//CMD2 sends R2 response, whose CMDINDEX field is wrong
if(r.getError()!=CmdResult::Ok && r.getError()!=CmdResult::RespNotMatch)
{
r.validateError();
return;
}
r=Command::send(Command::CMD3,0);
if(r.validateR6Response()==false) return;
Command::setRca(r.getResponse()>>16);
DBG("Got RCA=%u\n",Command::getRca());
if(Command::getRca()==0)
{
//RCA=0 can't be accepted, since it is used to deselect cards
DBGERR("RCA=0 is invalid\n");
return;
}
//Lastly, try selecting the card and configure the latest bits
{
CardSelector selector;
if(selector.succeded()==false) return;
r=Command::send(Command::CMD13,Command::getRca()<<16);//Get status
if(r.validateR1Response()==false) return;
if(r.getState()!=4) //4=Tran state
{
DBGERR("CMD7 was not able to select card\n");
return;
}
r=Command::send(Command::ACMD6,2); //Set 4 bit bus width
if(r.validateR1Response()==false) return;
if(cardType!=SDHC)
{
r=Command::send(Command::CMD16,512); //Set 512Byte block length
if(r.validateR1Response()==false) return;
}
}
// Now that card is initialized, perform self calibration of maximum
// possible read/write speed. This as a side effect enables 4bit bus width.
ClockController::calibrateClockSpeed(this);
DBG("SDIO init: Success\n");
}
} //namespace miosix