blob: 04ce94ded4f7bb040b34b9e76ee5f5f1a3633913 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* 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_hs.h"
#include <utility/assert.h>
#include <utility/trace.h>
#include <dmad/dmad.h>
#include <dma/dma.h>
//------------------------------------------------------------------------------
// Local constants
//------------------------------------------------------------------------------
/// Bit mask for status register errors.
#define STATUS_ERRORS (AT91C_MCI_UNRE \
| AT91C_MCI_OVRE \
| AT91C_MCI_BLKOVRE \
| AT91C_MCI_CSTOE \
| AT91C_MCI_DTOE \
| AT91C_MCI_DCRCE \
| AT91C_MCI_RTOE \
| AT91C_MCI_RENDE \
| AT91C_MCI_RCRCE \
| AT91C_MCI_RDIRE \
| AT91C_MCI_RINDE)
#define STATUS_ERRORS_RESP (AT91C_MCI_CSTOE \
| AT91C_MCI_RTOE \
| AT91C_MCI_RENDE \
| AT91C_MCI_RCRCE \
| AT91C_MCI_RDIRE \
| AT91C_MCI_RINDE)
#define STATUS_ERRORS_DATA (AT91C_MCI_UNRE \
| AT91C_MCI_OVRE \
| AT91C_MCI_BLKOVRE \
| AT91C_MCI_CSTOE \
| AT91C_MCI_DTOE \
| AT91C_MCI_DCRCE)
/// 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)
/// Enable MCI Clock
#define MCICK_ENABLE(pMciHw) WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIEN)
/// Disable MCI Clock
#define MCICK_DISABLE(pMciHw) WRITE_MCI(pMciHw, MCI_CR, AT91C_MCI_MCIDIS)
//------------------------------------------------------------------------------
// Local variables
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Internal Functions
//------------------------------------------------------------------------------
#if defined(MCI_DMA_ENABLE)
#define FIFO_SIZE (0x4000 - 0x200)
static DmaLinkList LLI_CH [4];
#define LAST_ROW 0x100
static void AT91F_Prepare_Multiple_Transfer(unsigned int Channel,
unsigned int LLI_rownumber,
unsigned int LLI_Last_Row,
unsigned int From_add,
unsigned int To_add,
unsigned int Ctrla,
unsigned int Ctrlb)
{
LLI_CH[LLI_rownumber].sourceAddress = From_add;
LLI_CH[LLI_rownumber].destAddress = To_add;
LLI_CH[LLI_rownumber].controlA = Ctrla;
LLI_CH[LLI_rownumber].controlB = Ctrlb;
if (LLI_Last_Row != LAST_ROW)
LLI_CH[LLI_rownumber].descriptor =
(unsigned int)&LLI_CH[LLI_rownumber + 1] + 0;
else
LLI_CH[LLI_rownumber].descriptor = 0;
}
static unsigned int DMACH_MCI_P2M(unsigned int channel_index,
unsigned int* src_addr,
unsigned int* dest_addr,
unsigned int trans_size,
unsigned char fifoForP)
{
unsigned int srcAddress;
unsigned int destAddress;
unsigned int buffSize;
unsigned int LLI_rownumber = 0;
unsigned int srcAddressMode = fifoForP ?
(AT91C_HDMA_SRC_ADDRESS_MODE_INCR)
: (AT91C_HDMA_SRC_ADDRESS_MODE_FIXED);
// Disable dma channel
DMA_DisableChannel(channel_index);
// DMA channel configuration
srcAddress = (unsigned int)src_addr; // Set the data start address
destAddress = (unsigned int)dest_addr; //(unsigned int)SSC_THR_ADD;
buffSize = trans_size;
if(buffSize >= 0x10000){
buffSize = 0xffff;
}
// Set DMA channel source address
DMA_SetSourceAddr(channel_index, srcAddress);
// Set DMA channel destination address
DMA_SetDestinationAddr(channel_index,destAddress);
// Set DMA channel DSCR
DMA_SetDescriptorAddr(channel_index, (unsigned int)&LLI_CH[0]);
// Set DMA channel control A
DMA_SetSourceBufferSize(channel_index, buffSize,
(AT91C_HDMA_SRC_WIDTH_WORD >> 24),
(AT91C_HDMA_DST_WIDTH_WORD >> 28), 0);
//Set DMA channel control B
DMA_SetSourceBufferMode(channel_index, DMA_TRANSFER_LLI,
srcAddressMode >> 24);
DMA_SetDestBufferMode(channel_index, DMA_TRANSFER_LLI,
(AT91C_HDMA_DST_ADDRESS_MODE_INCR >> 28));
// Set DMA channel config
DMA_SetConfiguration(channel_index, BOARD_SD_DMA_HW_SRC_REQ_ID \
| BOARD_SD_DMA_HW_DEST_REQ_ID \
| AT91C_HDMA_SRC_REP_CONTIGUOUS_ADDR \
| AT91C_HDMA_SRC_H2SEL_HW \
| AT91C_HDMA_DST_REP_CONTIGUOUS_ADDR \
| AT91C_HDMA_DST_H2SEL_SW \
| AT91C_HDMA_SOD_DISABLE \
| AT91C_HDMA_FIFOCFG_LARGESTBURST);
// Set link list
while(destAddress < ((unsigned int)(dest_addr + buffSize))) {
if(((unsigned int)(dest_addr + buffSize)) - destAddress <= (4*0xFFF) )
{
AT91F_Prepare_Multiple_Transfer(channel_index, LLI_rownumber, LAST_ROW,
srcAddress,
destAddress,
(((((unsigned int)(dest_addr + buffSize))
- destAddress)/4)
| AT91C_HDMA_SRC_WIDTH_WORD
| AT91C_HDMA_DST_WIDTH_WORD),
( AT91C_HDMA_SIF_0
| AT91C_HDMA_DIF_0
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM
//| AT91C_HDMA_DST_DSCR_FETCH_DISABLE
| AT91C_HDMA_DST_ADDRESS_MODE_INCR
//| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM
| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE
| srcAddressMode
| AT91C_HDMA_AUTO_DISABLE
| AT91C_HDMA_FC_PER2MEM));
}
else
{
AT91F_Prepare_Multiple_Transfer(channel_index, LLI_rownumber, 0,
srcAddress,
destAddress,
( 0xFFF
| AT91C_HDMA_SRC_WIDTH_WORD
| AT91C_HDMA_DST_WIDTH_WORD),
(AT91C_HDMA_SIF_0
| AT91C_HDMA_DIF_0
| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM
//| AT91C_HDMA_DST_DSCR_FETCH_DISABLE
| AT91C_HDMA_DST_ADDRESS_MODE_INCR
//| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM
| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE
| srcAddressMode
| AT91C_HDMA_AUTO_DISABLE
| AT91C_HDMA_FC_PER2MEM));
}
destAddress += 4*0xFFF;
LLI_rownumber++;
}
return 0;
}
static unsigned int DMACH_MCI_M2P(unsigned int channel_index,
unsigned int* src_addr,
unsigned int* dest_addr,
unsigned int trans_size,
unsigned char fifoForP)
{
unsigned int srcAddress;
unsigned int destAddress;
unsigned int buffSize;
unsigned int LLI_rownumber = 0;
unsigned int dstAddressMode = fifoForP ?
(AT91C_HDMA_DST_ADDRESS_MODE_INCR)
: (AT91C_HDMA_DST_ADDRESS_MODE_FIXED);
// Disable dma channel
DMA_DisableChannel(channel_index);
buffSize = trans_size;
if(buffSize >= 0x10000){
buffSize = 0xffff;
}
// DMA channel configuration
srcAddress = (unsigned int)src_addr; // Set the data start address
destAddress = (unsigned int)dest_addr;
// Set DMA channel source address
DMA_SetSourceAddr(channel_index, srcAddress);
// Set DMA channel destination address
DMA_SetDestinationAddr(channel_index,destAddress);
// Set DMA channel DSCR
DMA_SetDescriptorAddr(channel_index, (unsigned int)&LLI_CH[0]);
// Set DMA channel control A
DMA_SetSourceBufferSize(channel_index, buffSize,
(AT91C_HDMA_SRC_WIDTH_WORD >> 24),
(AT91C_HDMA_DST_WIDTH_WORD >> 28), 0);
//Set DMA channel control B
DMA_SetSourceBufferMode(channel_index,
DMA_TRANSFER_LLI,
(AT91C_HDMA_SRC_ADDRESS_MODE_INCR >> 24));
DMA_SetDestBufferMode(channel_index,
DMA_TRANSFER_LLI,
dstAddressMode >> 28);
// Set DMA channel config
DMA_SetConfiguration(channel_index, BOARD_SD_DMA_HW_SRC_REQ_ID \
| BOARD_SD_DMA_HW_DEST_REQ_ID \
| AT91C_HDMA_SRC_REP_CONTIGUOUS_ADDR \
| AT91C_HDMA_SRC_H2SEL_SW \
| AT91C_HDMA_DST_REP_CONTIGUOUS_ADDR \
| AT91C_HDMA_DST_H2SEL_HW \
| AT91C_HDMA_SOD_DISABLE \
| AT91C_HDMA_FIFOCFG_LARGESTBURST);
// Set link list
while(srcAddress < ((unsigned int)(src_addr + buffSize)))
{
if(((unsigned int)(src_addr + buffSize)) - srcAddress <= (4*0xFFF) )
{
AT91F_Prepare_Multiple_Transfer(channel_index, LLI_rownumber, LAST_ROW,
srcAddress,
destAddress,
(((((unsigned int)(src_addr + buffSize))
- srcAddress)/4)
| AT91C_HDMA_SRC_WIDTH_WORD
| AT91C_HDMA_DST_WIDTH_WORD),
( AT91C_HDMA_SIF_0
| AT91C_HDMA_DIF_0
//| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM
| AT91C_HDMA_DST_DSCR_FETCH_DISABLE
| dstAddressMode
//| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM
| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE
| AT91C_HDMA_SRC_ADDRESS_MODE_INCR
| AT91C_HDMA_AUTO_DISABLE
| AT91C_HDMA_FC_MEM2PER));
}
else
{
AT91F_Prepare_Multiple_Transfer(channel_index, LLI_rownumber, 0,
srcAddress,
destAddress,
( 0xFFF
| AT91C_HDMA_SRC_WIDTH_WORD
| AT91C_HDMA_DST_WIDTH_WORD),
( AT91C_HDMA_SIF_0
| AT91C_HDMA_DIF_0
//| AT91C_HDMA_DST_DSCR_FETCH_FROM_MEM
| AT91C_HDMA_DST_DSCR_FETCH_DISABLE
| dstAddressMode
| AT91C_HDMA_SRC_DSCR_FETCH_FROM_MEM
//| AT91C_HDMA_SRC_DSCR_FETCH_DISABLE
| AT91C_HDMA_SRC_ADDRESS_MODE_INCR
| AT91C_HDMA_AUTO_DISABLE
| AT91C_HDMA_FC_MEM2PER));
}
srcAddress += 4*0xFFF;
LLI_rownumber++;
}
return 0;
}
static inline void DMACH_EnableIt(AT91S_MCI *pMciHw,
unsigned int channel)
{
unsigned int intFlag;
intFlag = DMA_GetInterruptMask();
intFlag |= (AT91C_HDMA_BTC0 << channel);
DMA_EnableIt(intFlag);
}
#endif
//------------------------------------------------------------------------------
// 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) {
MCICK_DISABLE(pMciHw);
}
else {
MCICK_ENABLE(pMciHw);
}
}
//------------------------------------------------------------------------------
/// 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 supported card (max bus width).
//------------------------------------------------------------------------------
void MCI_Init(
Mci *pMci,
AT91S_MCI *pMciHw,
unsigned char mciId,
unsigned int mode)
{
unsigned short clkDiv;
unsigned int mciCfg = 0;
SANITY_CHECK(pMci);
SANITY_CHECK(pMciHw);
SANITY_CHECK( (mode == MCI_MMC_SLOTA) || (mode == MCI_SD_SLOTA)
|| (mode == MCI_MMC_SLOTB) || (mode == MCI_SD_SLOTB)
|| (mode == MCI_MMC4_SLOTA) || (mode == MCI_MMC4_SLOTB));
// Initialize the MCI driver structure
pMci->pMciHw = pMciHw;
pMci->mciId = mciId;
pMci->mciMode = mode;
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 / (MCI_INITIAL_SPEED * 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 DMA interface
WRITE_MCI(pMciHw, MCI_DMA, AT91C_MCI_DMAEN_DISABLE);
// Configure MCI
//mciCfg = AT91C_MCI_FIFOMODE_AMOUNTDATA | AT91C_MCI_FERRCTRL_RWCMD;
mciCfg = AT91C_MCI_FIFOMODE_ONEDATA | AT91C_MCI_FERRCTRL_RWCMD;
WRITE_MCI(pMciHw, MCI_CFG, mciCfg);
// 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);
}
//------------------------------------------------------------------------------
/// Get 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.
//------------------------------------------------------------------------------
unsigned int MCI_GetSpeed(Mci *pMci, unsigned int *mciDiv)
{
AT91S_MCI *pMciHw = pMci->pMciHw;
unsigned int mciMr;
SANITY_CHECK(pMci);
SANITY_CHECK(pMci->pMciHw);
// Get the Mode Register
mciMr = READ_MCI(pMciHw, MCI_MR);
mciMr &= AT91C_MCI_CLKDIV;
if (mciDiv) *mciDiv = mciMr;
return (BOARD_MCK / 2 / (mciMr + 1));
}
//------------------------------------------------------------------------------
/// 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.
/// \param mciLimit MCI clock limit in Hz, if not limit, set mciLimit to zero.
/// \return The actual speed used, 0 for fail.
//------------------------------------------------------------------------------
unsigned int MCI_SetSpeed(Mci *pMci,
unsigned int mciSpeed,
unsigned int mciLimit)
{
AT91S_MCI *pMciHw = pMci->pMciHw;
unsigned int mciMr;
unsigned int clkdiv;
unsigned int divLimit = 0;
SANITY_CHECK(pMci);
SANITY_CHECK(pMci->pMciHw);
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))
// mciSpeed = MCK / (2*(CLKDIV+1))
if (mciLimit) divLimit = (BOARD_MCK / 2 / mciLimit);
if (mciSpeed > 0) {
clkdiv = (BOARD_MCK / 2 / mciSpeed);
if (mciLimit && clkdiv < divLimit)
clkdiv = divLimit;
if (clkdiv > 0)
clkdiv -= 1;
ASSERT( (clkdiv & 0xFFFFFF00) == 0, "mciSpeed too small");
}
else clkdiv = 0;
WRITE_MCI(pMciHw, MCI_MR, mciMr | clkdiv);
return (BOARD_MCK / 2 / (clkdiv + 1));
}
//------------------------------------------------------------------------------
/// Configure the MCI_CFG to enable the HS mode
/// \param pMci Pointer to the low level MCI driver.
/// \param hsEnable 1 to enable, 0 to disable HS mode.
//------------------------------------------------------------------------------
void MCI_EnableHsMode(Mci *pMci, unsigned char hsEnable)
{
AT91S_MCI *pMciHw = pMci->pMciHw;
unsigned int cfgr;
SANITY_CHECK(pMci);
SANITY_CHECK(pMci->pMciHw);
cfgr = READ_MCI(pMciHw, MCI_CFG);
if (hsEnable) cfgr |= AT91C_MCI_HSMODE_ENABLE;
else cfgr &= ~AT91C_MCI_HSMODE_ENABLE;
}
//------------------------------------------------------------------------------
/// 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;
unsigned int transSize;
unsigned int mciBlkr;
#if defined(MCI_DMA_ENABLE)
unsigned int mciDma;
#endif
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--;
// Command is now being executed
pMci->pCommand = pCommand;
pCommand->status = MCI_STATUS_PENDING;
// Enable the MCI peripheral clock
WRITE_PMC(AT91C_BASE_PMC, PMC_PCER, (1 << pMci->mciId));
// Disable MCI clock, for multi-block data transfer
MCICK_DISABLE(pMciHw);
// Set Default Mode register value
mciMr = READ_MCI(pMciHw, MCI_MR) & (~( AT91C_MCI_WRPROOF
|AT91C_MCI_RDPROOF
|AT91C_MCI_BLKLEN));
// Command with DATA stage
if (pCommand->blockSize && pCommand->nbBlock) {
// Enable dma
#if defined(MCI_DMA_ENABLE)
mciDma = READ_MCI(pMciHw, MCI_DMA) | AT91C_MCI_DMAEN_ENABLE;
WRITE_MCI(pMciHw, MCI_DMA, mciDma);
#endif
// New transfer
if(pCommand->tranType == MCI_NEW_TRANSFER) {
// Set block size
WRITE_MCI(pMciHw, MCI_MR, mciMr | AT91C_MCI_RDPROOF
| AT91C_MCI_WRPROOF
|(pCommand->blockSize << 16));
mciBlkr = READ_MCI(pMciHw, MCI_BLKR) & (~AT91C_MCI_BCNT);
WRITE_MCI(pMciHw, MCI_BLKR, mciBlkr | pCommand->nbBlock);
}
transSize = (pCommand->nbBlock * pCommand->blockSize) / 4;
if ((pCommand->blockSize & 0x3) != 0)
transSize++;
// DATA transfer from card to host
if (pCommand->isRead) {
#if defined(MCI_DMA_ENABLE)
DMACH_MCI_P2M(BOARD_MCI_DMA_CHANNEL,
(unsigned int*)&pMciHw->MCI_FIFO,
(unsigned int*) pCommand->pData,
transSize, 1);
DMACH_EnableIt(pMciHw, BOARD_MCI_DMA_CHANNEL);
DMA_EnableChannel(BOARD_MCI_DMA_CHANNEL);
mciIer = AT91C_MCI_DMADONE | STATUS_ERRORS;
#else
mciIer = AT91C_MCI_CMDRDY | STATUS_ERRORS;
#endif
}
// DATA transfer from host to card
else {
#if defined(MCI_DMA_ENABLE)
DMACH_MCI_M2P(BOARD_MCI_DMA_CHANNEL,
(unsigned int*) pCommand->pData,
(unsigned int*)&pMciHw->MCI_FIFO,
transSize, 1);
DMACH_EnableIt(pMciHw, BOARD_MCI_DMA_CHANNEL);
DMA_EnableChannel(BOARD_MCI_DMA_CHANNEL);
mciIer = AT91C_MCI_DMADONE | STATUS_ERRORS;
#else
mciIer = AT91C_MCI_CMDRDY | STATUS_ERRORS;
#endif
}
}
// Start an infinite block transfer (but no data in current command)
else if (pCommand->dataTran) {
// Set block size
WRITE_MCI(pMciHw, MCI_MR, mciMr | AT91C_MCI_RDPROOF
| AT91C_MCI_WRPROOF
|(pCommand->blockSize << 16));
// Set data length: 0
mciBlkr = READ_MCI(pMciHw, MCI_BLKR) & (~AT91C_MCI_BCNT);
WRITE_MCI(pMciHw, MCI_BLKR, mciBlkr);
mciIer = AT91C_MCI_CMDRDY | 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
MCICK_ENABLE(pMciHw);
// Send the command
if((pCommand->tranType != MCI_CONTINUE_TRANSFER)
|| (pCommand->blockSize == 0)) {
WRITE_MCI(pMciHw, MCI_ARGR, pCommand->arg);
WRITE_MCI(pMciHw, MCI_CMDR, pCommand->cmd);
}
// Ignore data error
mciIer &= ~( AT91C_MCI_UNRE
| AT91C_MCI_OVRE
| AT91C_MCI_DTOE
| AT91C_MCI_DCRCE
| AT91C_MCI_BLKOVRE
| AT91C_MCI_CSTOE);
// 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;
volatile unsigned int status;
// Enable MCI clock
MCICK_ENABLE(pMciHw);
status = READ_MCI(pMciHw, MCI_SR);
if( ((status & AT91C_MCI_NOTBUSY)!=0)
&& ((status & AT91C_MCI_DTIP)==0)
) {
// Disable MCI clock
MCICK_DISABLE(pMciHw);
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;
volatile MciCmd *pCommand = pMci->pCommand;
volatile unsigned int status, status0, mask;
unsigned char i;
SANITY_CHECK(pMci);
SANITY_CHECK(pMciHw);
SANITY_CHECK(pCommand);
// Read the status register
status0 = READ_MCI(pMciHw, MCI_SR);
mask = READ_MCI(pMciHw, MCI_IMR);
//TRACE_INFO("iST %x\n\r", status);
status = status0 & mask;
//TRACE_INFO("iSM %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;
}
// printf("iErr%x\n\r", (status & STATUS_ERRORS));
}
mask &= ~STATUS_ERRORS;
// Check if a command has been completed
if (status & AT91C_MCI_CMDRDY) {
WRITE_MCI(pMciHw, MCI_IDR, AT91C_MCI_CMDRDY);
if (pCommand->isRead == 0 &&
pCommand->tranType == MCI_STOP_TRANSFER) {
if (status0 & AT91C_MCI_XFRDONE) {
MCICK_DISABLE(pMciHw);
}
else {
WRITE_MCI(pMciHw, MCI_IER, AT91C_MCI_XFRDONE);
}
}
else {
mask &= ~AT91C_MCI_CMDRDY;
if (pCommand->dataTran == 0) {
MCICK_DISABLE(pMciHw);
}
}
}
// Check if transfer stopped
if (status & AT91C_MCI_XFRDONE) {
mask &= ~AT91C_MCI_XFRDONE;
MCICK_DISABLE(pMciHw);
}
#if defined(MCI_DMA_ENABLE)
// Check FIFOEMPTY
if (status & AT91C_MCI_FIFOEMPTY) {
mask &= ~AT91C_MCI_FIFOEMPTY;
MCICK_DISABLE(pMciHw);
}
// Check if a DMA transfer has been completed
if (status & AT91C_MCI_DMADONE) {
unsigned int intFlag;
intFlag = DMA_GetInterruptMask();
intFlag = ~intFlag;
intFlag |= (AT91C_HDMA_BTC0 << BOARD_MCI_DMA_CHANNEL);
DMA_DisableIt(intFlag);
WRITE_MCI(pMciHw, MCI_IDR, AT91C_MCI_DMADONE);
if ( pCommand->isRead == 0 &&
(status0 & AT91C_MCI_FIFOEMPTY) == 0 ) {
WRITE_MCI(pMciHw, MCI_IER, AT91C_MCI_FIFOEMPTY);
}
else {
MCICK_DISABLE(pMciHw);
mask &= ~AT91C_MCI_DMADONE;
}
}
#endif
// All non-error mask done, complete the command
if (!mask || pCommand->status != MCI_STATUS_PENDING) {
// Store the card response in the provided buffer
if (pCommand->pResp) {
unsigned char resSize;
switch (pCommand->resType) {
case 1: case 3: case 4: case 5: case 6: case 7:
resSize = 1; break;
case 2: resSize = 4; break;
default: resSize = 0; break;
}
for (i=0; i < resSize; i++) {
pCommand->pResp[i] = READ_MCI(pMciHw, MCI_RSPR[0]);
}
}
// If no error occured, the transfer is successful
if (pCommand->status == MCI_STATUS_PENDING)
pCommand->status = 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, (void*)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;
}
}
//------------------------------------------------------------------------------
/// Check whether the MCI is using the FIFO transfer mode
/// \param pMci Pointer to a Mci instance.
/// \param pCommand Pointer to a MciCmd instance.
//------------------------------------------------------------------------------
unsigned int MCI_FifoTransfer(Mci *pMci, MciCmd *pCommand)
{
unsigned int status=0;
unsigned int nbTransfer=0;
unsigned int i;
AT91S_MCI *pMciHw = pMci->pMciHw;
unsigned int *pMem;
SANITY_CHECK(pMci);
SANITY_CHECK(pCommand);
// If using DMA mode, return
#if defined(MCI_DMA_ENABLE)
return 0;
#endif
TRACE_DEBUG("MCIFifo:%d,%d\n\r", pCommand->isRead, pCommand->nbBlock);
if (pCommand->nbBlock == 0 || pCommand->blockSize == 0)
return 0;
pMem = (unsigned int*)pCommand->pData;
// Get transfer size
nbTransfer = (pCommand->blockSize) * (pCommand->nbBlock) / 4;
if((pCommand->blockSize) * (pCommand->nbBlock) % 4) {
nbTransfer++;
}
if (pCommand->isRead) {
// Read RDR loop
for(i=0; i<nbTransfer; i++) {
while(1) {
status = READ_MCI(pMciHw, MCI_SR);
if (status & AT91C_MCI_RXRDY)
break;
#if 1
if (status & STATUS_ERRORS_DATA) {
TRACE_ERROR("MCI_FifoTransfer.R: 0x%x\n\r", status);
return status;
}
#endif
}
*pMem = READ_MCI(pMciHw, MCI_RDR);
pMem++;
}
}
else {
// Write TDR loop
for(i=0; i<nbTransfer; i++) {
while(1) {
status = READ_MCI(pMciHw, MCI_SR);
if (status & (AT91C_MCI_TXRDY | AT91C_MCI_NOTBUSY))
break;
#if 0
if (status & STATUS_ERRORS_DATA) {
TRACE_ERROR("MCI_FifoTransfer.W: 0x%x\n\r", status);
return status;
}
#endif
}
WRITE_MCI(pMciHw, MCI_TDR, *pMem);
pMem++;
}
}
status = READ_MCI(pMciHw, MCI_SR);
TRACE_DEBUG("MCI_FifoTransfer : All status %x\n\r", status);
status &= READ_MCI(pMciHw, MCI_IMR);
TRACE_DEBUG("MCI_FifoTransfer : Masked status %x\n\r", status);
#if 0
{ unsigned int old = status;
while(status & AT91C_MCI_DTIP) {
status = READ_MCI(pMciHw, MCI_SR);
if (status != old) {
old = status;
TRACE_DEBUG_WP(" -> %x", status);
}
}
TRACE_DEBUG_WP("\n\r");
TRACE_DEBUG(" DPIT 0 stat %x\n\r", status);
while((status & (AT91C_MCI_FIFOEMPTY
| AT91C_MCI_BLKE
| AT91C_MCI_XFRDONE)) == 0) {
status = READ_MCI(pMciHw, MCI_SR);
}
TRACE_DEBUG(" FIFO EMPTY stat %x\n\r", status);
}
#endif
return status;
}