/* ---------------------------------------------------------------------------- | |
* ATMEL Microcontroller Software Support | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2008, Atmel Corporation | |
* | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the disclaimer below. | |
* | |
* Atmel's name may not be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* ---------------------------------------------------------------------------- | |
*/ | |
//------------------------------------------------------------------------------ | |
// Headers | |
//------------------------------------------------------------------------------ | |
#include "mci.h" | |
#include <utility/assert.h> | |
#include <utility/trace.h> | |
//------------------------------------------------------------------------------ | |
// Local constants | |
//------------------------------------------------------------------------------ | |
/// Bit mask for status register errors. | |
#define STATUS_ERRORS (AT91C_MCI_UNRE \ | |
| AT91C_MCI_OVRE \ | |
| AT91C_MCI_DTOE \ | |
| AT91C_MCI_DCRCE \ | |
| AT91C_MCI_RTOE \ | |
| AT91C_MCI_RENDE \ | |
| AT91C_MCI_RCRCE \ | |
| AT91C_MCI_RDIRE \ | |
| AT91C_MCI_RINDE) | |
/// MCI data timeout configuration with 1048576 MCK cycles between 2 data transfers. | |
#define DTOR_1MEGA_CYCLES (AT91C_MCI_DTOCYC | AT91C_MCI_DTOMUL) | |
/// MCI MR: disable MCI Clock when FIFO is full | |
#ifndef AT91C_MCI_WRPROOF | |
#define AT91C_MCI_WRPROOF 0 | |
#endif | |
#ifndef AT91C_MCI_RDPROOF | |
#define AT91C_MCI_RDPROOF 0 | |
#endif | |
#define SDCARD_APP_OP_COND_CMD (41 | AT91C_MCI_SPCMD_NONE | AT91C_MCI_RSPTYP_48 | AT91C_MCI_TRCMD_NO ) | |
#define MMC_SEND_OP_COND_CMD (1 | AT91C_MCI_TRCMD_NO | AT91C_MCI_SPCMD_NONE | AT91C_MCI_RSPTYP_48 | AT91C_MCI_OPDCMD) | |
#define DISABLE 0 // Disable MCI interface | |
#define ENABLE 1 // Enable MCI interface | |
//------------------------------------------------------------------------------ | |
// Local macros | |
//------------------------------------------------------------------------------ | |
/// Used to write in PMC registers. | |
#define WRITE_PMC(pPmc, regName, value) pPmc->regName = (value) | |
/// Used to write in MCI registers. | |
#define WRITE_MCI(pMci, regName, value) pMci->regName = (value) | |
/// Used to read from MCI registers. | |
#define READ_MCI(pMci, regName) (pMci->regName) | |
//------------------------------------------------------------------------------ | |
// Global functions | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
/// Enable/disable a MCI driver instance. | |
/// \param pMci Pointer to a MCI driver instance. | |
/// \param enb 0 for disable MCI and 1 for enable MCI. | |
//------------------------------------------------------------------------------ | |
void MCI_Enable(Mci *pMci, unsigned char enb) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMci->pMciHw); | |
// Set the Control Register: Enable/Disable MCI interface clock | |
if(enb == DISABLE) { | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIDIS); | |
} | |
else { | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIEN); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
/// Initializes a MCI driver instance and the underlying peripheral. | |
/// \param pMci Pointer to a MCI driver instance. | |
/// \param pMciHw Pointer to a MCI peripheral. | |
/// \param mciId MCI peripheral identifier. | |
/// \param mode Slot and type of connected card. | |
//------------------------------------------------------------------------------ | |
void MCI_Init( | |
Mci *pMci, | |
AT91S_MCI *pMciHw, | |
unsigned char mciId, | |
unsigned int mode) | |
{ | |
unsigned short clkDiv; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMciHw); | |
SANITY_CHECK((mode == MCI_MMC_SLOTA) || (mode == MCI_MMC_SLOTB) | |
|| (mode == MCI_SD_SLOTA) || (mode == MCI_SD_SLOTB)); | |
// Initialize the MCI driver structure | |
pMci->pMciHw = pMciHw; | |
pMci->mciId = mciId; | |
pMci->semaphore = 1; | |
pMci->pCommand = 0; | |
// Enable the MCI clock | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCER, (1 << mciId)); | |
// Reset the MCI | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_SWRST); | |
// Disable the MCI | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIDIS | AT91C_MCI_PWSDIS); | |
// Disable all the interrupts | |
WRITE_MCI(pMciHw, MCI_IDR, 0xFFFFFFFF); | |
// Set the Data Timeout Register | |
WRITE_MCI(pMciHw, MCI_DTOR, DTOR_1MEGA_CYCLES); | |
// Set the Mode Register: 400KHz for MCK = 48MHz (CLKDIV = 58) | |
clkDiv = (BOARD_MCK / (400000 * 2)) - 1; | |
WRITE_MCI(pMciHw, MCI_MR, (clkDiv | (AT91C_MCI_PWSDIV & (0x7 << 8)))); | |
// Set the SDCard Register | |
WRITE_MCI(pMciHw, MCI_SDCR, mode); | |
// Enable the MCI and the Power Saving | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIEN); | |
// Disable the MCI peripheral clock. | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCDR, (1 << mciId)); | |
} | |
//------------------------------------------------------------------------------ | |
/// Close a MCI driver instance and the underlying peripheral. | |
/// \param pMci Pointer to a MCI driver instance. | |
/// \param pMciHw Pointer to a MCI peripheral. | |
/// \param mciId MCI peripheral identifier. | |
//------------------------------------------------------------------------------ | |
void MCI_Close(Mci *pMci) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMciHw); | |
// Initialize the MCI driver structure | |
pMci->semaphore = 1; | |
pMci->pCommand = 0; | |
// Disable the MCI peripheral clock. | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCDR, (1 << pMci->mciId)); | |
// Disable the MCI | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIDIS); | |
// Disable all the interrupts | |
WRITE_MCI(pMciHw, MCI_IDR, 0xFFFFFFFF); | |
} | |
//------------------------------------------------------------------------------ | |
/// Configure the MCI CLKDIV in the MCI_MR register. The max. for MCI clock is | |
/// MCK/2 and corresponds to CLKDIV = 0 | |
/// \param pMci Pointer to the low level MCI driver. | |
/// \param mciSpeed MCI clock speed in Hz. | |
//------------------------------------------------------------------------------ | |
void MCI_SetSpeed(Mci *pMci, unsigned int mciSpeed) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
unsigned int mciMr; | |
unsigned int clkdiv; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMci->pMciHw); | |
// Set the Mode Register: 400KHz for MCK = 48MHz (CLKDIV = 58) | |
mciMr = READ_MCI(pMciHw, MCI_MR) & (~AT91C_MCI_CLKDIV); | |
// Multimedia Card Interface clock (MCCK or MCI_CK) is Master Clock (MCK) | |
// divided by (2*(CLKDIV+1)) | |
if (mciSpeed > 0) { | |
clkdiv = (BOARD_MCK / (mciSpeed * 2)); | |
if (clkdiv > 0) { | |
clkdiv -= 1; | |
} | |
ASSERT( (clkdiv & 0xFFFFFF00) == 0, "mciSpeed too small"); | |
} | |
else { | |
clkdiv = 0; | |
} | |
WRITE_MCI(pMciHw, MCI_MR, mciMr | clkdiv); | |
} | |
//------------------------------------------------------------------------------ | |
/// Configure the MCI SDCBUS in the MCI_SDCR register. Only two modes available | |
/// | |
/// \param pMci Pointer to the low level MCI driver. | |
/// \param busWidth MCI bus width mode. | |
//------------------------------------------------------------------------------ | |
void MCI_SetBusWidth(Mci *pMci, unsigned char busWidth) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
unsigned int mciSdcr; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMci->pMciHw); | |
mciSdcr = (READ_MCI(pMciHw, MCI_SDCR) & ~(AT91C_MCI_SCDBUS)); | |
WRITE_MCI(pMciHw, MCI_SDCR, mciSdcr | busWidth); | |
} | |
//------------------------------------------------------------------------------ | |
/// Starts a MCI transfer. This is a non blocking function. It will return | |
/// as soon as the transfer is started. | |
/// Return 0 if successful; otherwise returns MCI_ERROR_LOCK if the driver is | |
/// already in use. | |
/// \param pMci Pointer to an MCI driver instance. | |
/// \param pCommand Pointer to the command to execute. | |
//------------------------------------------------------------------------------ | |
unsigned char MCI_SendCommand(Mci *pMci, MciCmd *pCommand) | |
{ | |
AT91PS_MCI pMciHw = pMci->pMciHw; | |
unsigned int mciIer, mciMr; | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMciHw); | |
SANITY_CHECK(pCommand); | |
// Try to acquire the MCI semaphore | |
if (pMci->semaphore == 0) { | |
return MCI_ERROR_LOCK; | |
} | |
pMci->semaphore--; | |
// TRACE_DEBUG("MCI_SendCommand %x %d\n\r", READ_MCI(pMciHw, MCI_SR), pCommand->cmd & 0x3f); | |
// Command is now being executed | |
pMci->pCommand = pCommand; | |
pCommand->status = MCI_STATUS_PENDING; | |
// Enable the MCI clock | |
WRITE_PMC(AT91C_BASE_PMC, PMC_PCER, (1 << pMci->mciId)); | |
//Disable MCI clock, for multi-block data transfer | |
MCI_Enable(pMci, DISABLE); | |
// Set PDC data transfer direction | |
if(pCommand->blockSize > 0) { | |
if(pCommand->isRead) { | |
WRITE_MCI(pMciHw, MCI_PTCR, AT91C_PDC_RXTEN); | |
} | |
else { | |
WRITE_MCI(pMciHw, MCI_PTCR, AT91C_PDC_TXTEN); | |
} | |
} | |
// Disable transmitter and receiver | |
WRITE_MCI(pMciHw, MCI_PTCR, AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS); | |
mciMr = READ_MCI(pMciHw, MCI_MR) & (~(AT91C_MCI_WRPROOF|AT91C_MCI_RDPROOF|AT91C_MCI_BLKLEN | AT91C_MCI_PDCMODE)); | |
// Command with DATA stage | |
if (pCommand->blockSize > 0) { | |
// Enable PDC mode and set block size | |
if(pCommand->conTrans != MCI_CONTINUE_TRANSFER) { | |
WRITE_MCI(pMciHw, MCI_MR, mciMr | AT91C_MCI_PDCMODE |AT91C_MCI_RDPROOF|AT91C_MCI_WRPROOF|(pCommand->blockSize << 16)); | |
} | |
// DATA transfer from card to host | |
if (pCommand->isRead) { | |
WRITE_MCI(pMciHw, MCI_RPR, (int) pCommand->pData); | |
// Sanity check | |
if (pCommand->nbBlock == 0) | |
pCommand->nbBlock = 1; | |
//////// | |
if ((pCommand->blockSize & 0x3) != 0) { | |
WRITE_MCI(pMciHw, MCI_RCR, (pCommand->nbBlock * pCommand->blockSize) / 4 + 1); | |
} | |
else { | |
WRITE_MCI(pMciHw, MCI_RCR, (pCommand->nbBlock * pCommand->blockSize) / 4); | |
} | |
WRITE_MCI(pMciHw, MCI_PTCR, AT91C_PDC_RXTEN); | |
mciIer = AT91C_MCI_ENDRX | STATUS_ERRORS; | |
// mciIer = AT91C_MCI_RXBUFF | STATUS_ERRORS; | |
} | |
// DATA transfer from host to card | |
else { | |
// Sanity check | |
if (pCommand->nbBlock == 0) | |
pCommand->nbBlock = 1; | |
WRITE_MCI(pMciHw, MCI_TPR, (int) pCommand->pData); | |
// Update the PDC counter | |
if ((pCommand->blockSize & 0x3) != 0) { | |
WRITE_MCI(pMciHw, MCI_TCR, (pCommand->nbBlock * pCommand->blockSize) / 4 + 1); | |
} | |
else { | |
WRITE_MCI(pMciHw, MCI_TCR, (pCommand->nbBlock * pCommand->blockSize) / 4); | |
} | |
// MCI_BLKE notifies the end of Multiblock command | |
mciIer = AT91C_MCI_BLKE | STATUS_ERRORS; | |
} | |
} | |
// No data transfer: stop at the end of the command | |
else { | |
WRITE_MCI(pMciHw, MCI_MR, mciMr); | |
mciIer = AT91C_MCI_CMDRDY | STATUS_ERRORS; | |
} | |
// Enable MCI clock | |
MCI_Enable(pMci, ENABLE); | |
// Send the command | |
if((pCommand->conTrans != MCI_CONTINUE_TRANSFER) | |
|| (pCommand->blockSize == 0)) { | |
WRITE_MCI(pMciHw, MCI_ARGR, pCommand->arg); | |
WRITE_MCI(pMciHw, MCI_CMDR, pCommand->cmd); | |
} | |
// In case of transmit, the PDC shall be enabled after sending the command | |
if ((pCommand->blockSize > 0) && !(pCommand->isRead)) { | |
WRITE_MCI(pMciHw, MCI_PTCR, AT91C_PDC_TXTEN); | |
} | |
// Ignore data error | |
mciIer &= ~(AT91C_MCI_UNRE | AT91C_MCI_OVRE \ | |
| AT91C_MCI_DTOE | AT91C_MCI_DCRCE); | |
// Interrupt enable shall be done after PDC TXTEN and RXTEN | |
WRITE_MCI(pMciHw, MCI_IER, mciIer); | |
return 0; | |
} | |
//------------------------------------------------------------------------------ | |
/// Check NOTBUSY and DTIP bits of status register on the given MCI driver. | |
/// Return value, 0 for bus ready, 1 for bus busy | |
/// \param pMci Pointer to a MCI driver instance. | |
//------------------------------------------------------------------------------ | |
unsigned char MCI_CheckBusy(Mci *pMci) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
unsigned int status; | |
// Enable MCI clock | |
MCI_Enable(pMci, ENABLE); | |
status = READ_MCI(pMciHw, MCI_SR); | |
// TRACE_DEBUG("status %x\n\r",status); | |
if(((status & AT91C_MCI_NOTBUSY)!=0) | |
&& ((status & AT91C_MCI_DTIP)==0)) { | |
// Disable MCI clock | |
MCI_Enable(pMci, DISABLE); | |
return 0; | |
} | |
else { | |
return 1; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
/// Check BLKE bit of status register on the given MCI driver. | |
/// \param pMci Pointer to a MCI driver instance. | |
//------------------------------------------------------------------------------ | |
unsigned char MCI_CheckBlke(Mci *pMci) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
unsigned int status; | |
status = READ_MCI(pMciHw, MCI_SR); | |
// TRACE_DEBUG("status %x\n\r",status); | |
if((status & AT91C_MCI_BLKE)!=0) { | |
return 0; | |
} | |
else { | |
return 1; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
/// Processes pending events on the given MCI driver. | |
/// \param pMci Pointer to a MCI driver instance. | |
//------------------------------------------------------------------------------ | |
void MCI_Handler(Mci *pMci) | |
{ | |
AT91S_MCI *pMciHw = pMci->pMciHw; | |
MciCmd *pCommand = pMci->pCommand; | |
unsigned int status; | |
unsigned char i; | |
#if defined(at91rm9200) | |
unsigned int mciCr, mciSdcr, mciMr, mciDtor; | |
#endif | |
SANITY_CHECK(pMci); | |
SANITY_CHECK(pMciHw); | |
SANITY_CHECK(pCommand); | |
// Read the status register | |
status = READ_MCI(pMciHw, MCI_SR) & READ_MCI(pMciHw, MCI_IMR); | |
// TRACE_DEBUG("status %x\n\r", status); | |
// Check if an error has occured | |
if ((status & STATUS_ERRORS) != 0) { | |
// Check error code | |
if ((status & STATUS_ERRORS) == AT91C_MCI_RTOE) { | |
pCommand->status = MCI_STATUS_NORESPONSE; | |
} | |
// if the command is SEND_OP_COND the CRC error flag is always present | |
// (cf : R3 response) | |
else if (((status & STATUS_ERRORS) != AT91C_MCI_RCRCE) | |
|| ((pCommand->cmd != SDCARD_APP_OP_COND_CMD) | |
&& (pCommand->cmd != MMC_SEND_OP_COND_CMD))) { | |
pCommand->status = MCI_STATUS_ERROR; | |
} | |
} | |
// Check if a transfer has been completed | |
if (((status & AT91C_MCI_CMDRDY) != 0) | |
|| ((status & AT91C_MCI_ENDRX) != 0) | |
|| ((status & AT91C_MCI_RXBUFF) != 0) | |
|| ((status & AT91C_MCI_ENDTX) != 0) | |
|| ((status & AT91C_MCI_BLKE) != 0) | |
|| ((status & AT91C_MCI_RTOE) != 0)) { | |
if (((status & AT91C_MCI_ENDRX) != 0) | |
|| ((status & AT91C_MCI_RXBUFF) != 0) | |
|| ((status & AT91C_MCI_ENDTX) != 0)) { | |
MCI_Enable(pMci, DISABLE); | |
} | |
/// On AT91RM9200-EK, if stop transmission, software reset MCI. | |
#if defined(at91rm9200) | |
if ((pCommand->cmd & AT91C_MCI_TRCMD_STOP) != 0) { | |
mciMr = READ_MCI(pMciHw, MCI_MR); | |
mciSdcr = READ_MCI(pMciHw, MCI_SDCR); | |
mciDtor = READ_MCI(pMciHw, MCI_DTOR); | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_SWRST); | |
// TRACE_DEBUG("reset MCI\n\r"); | |
WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIDIS | AT91C_MCI_PWSDIS); | |
WRITE_MCI(pMciHw, MCI_MR, mciMr); | |
WRITE_MCI(pMciHw, MCI_SDCR, mciSdcr); | |
WRITE_MCI(pMciHw, MCI_DTOR, mciDtor); | |
} | |
#endif | |
// If no error occured, the transfer is successful | |
if (pCommand->status == MCI_STATUS_PENDING) { | |
pCommand->status = 0; | |
} | |
#if 0 | |
if ((status & AT91C_MCI_CMDRDY) != 0) | |
TRACE_DEBUG_WP("."); | |
if ((status & AT91C_MCI_ENDRX) != 0) | |
TRACE_DEBUG_WP("<"); | |
if ((status & AT91C_MCI_ENDTX) != 0) | |
TRACE_DEBUG_WP("-"); | |
if ((status & AT91C_MCI_BLKE) != 0) | |
TRACE_DEBUG_WP(">"); | |
TRACE_DEBUG_WP("\n\r"); | |
#endif | |
// Store the card response in the provided buffer | |
if (pCommand->pResp) { | |
unsigned char resSize; | |
switch (pCommand->resType) { | |
case 1: | |
resSize = 1; | |
break; | |
case 2: | |
resSize = 4; | |
break; | |
case 3: | |
resSize = 1; | |
break; | |
case 4: | |
resSize = 1; | |
break; | |
case 5: | |
resSize = 1; | |
break; | |
case 6: | |
resSize = 1; | |
break; | |
case 7: | |
resSize = 1; | |
break; | |
default: | |
resSize = 0; | |
break; | |
} | |
for (i=0; i < resSize; i++) { | |
pCommand->pResp[i] = READ_MCI(pMciHw, MCI_RSPR[0]); | |
} | |
} | |
// Disable interrupts | |
WRITE_MCI(pMciHw, MCI_IDR, READ_MCI(pMciHw, MCI_IMR)); | |
// Release the semaphore | |
pMci->semaphore++; | |
// Invoke the callback associated with the current command (if any) | |
if (pCommand->callback) { | |
(pCommand->callback)(pCommand->status, pCommand); | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
/// Returns 1 if the given MCI transfer is complete; otherwise returns 0. | |
/// \param pCommand Pointer to a MciCmd instance. | |
//------------------------------------------------------------------------------ | |
unsigned char MCI_IsTxComplete(MciCmd *pCommand) | |
{ | |
if (pCommand->status != MCI_STATUS_PENDING) { | |
if (pCommand->status != 0) { | |
TRACE_DEBUG("MCI_IsTxComplete %d\n\r", pCommand->status); | |
} | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} |