/* ---------------------------------------------------------------------------- | |
* 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; | |
} |