blob: 6c5f1f1528599cea0cabb072a21f6fe3f959b652 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2013, 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.
* ----------------------------------------------------------------------------
*/
/**
* \addtogroup qspi_dma_module QSPI xDMA driver
* \ingroup peripherals_module
*
*
*/
/**
* \file
*
* Implementation for the SPI Flash with xDMA driver.
*
*/
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "chip.h"
/*----------------------------------------------------------------------------
* Definitions
*----------------------------------------------------------------------------*/
/** xDMA support */
/** xDMA Link List size for SPI transmission*/
#define DMA_QSPI_LLI 2
/*-----------------------------------------------------------------------------
* QSPI DMA Local functions
*----------------------------------------------------------------------------*/
/**
* \brief SPI xDMA Rx callback
* Invoked on SPi DMA reception done.
* \param channel DMA channel.
* \param pArg Pointer to callback argument - Pointer to Spid instance.
*/
static void QSPID_Spi_Cb(uint32_t channel, QspiDma_t* pArg)
{
Qspi *pQspiHw = pArg->Qspid.pQspiHw;
if (channel != pArg->RxChNum)
return;
/* Release the semaphore */
ReleaseMutex(pArg->progress);
QSPI_EndTransfer(pQspiHw);
memory_sync();
}
/**
* \brief QSPI xDMA Tx callback
* Invoked on QSPi DMA Write done.
* \param channel DMA channel.
* \param pArg Pointer to callback argument - Pointer to Spid instance.
*/
static void QSPID_qspiTx_Cb(uint32_t channel, QspiDma_t* pArg)
{
Qspi *pQspiHw = pArg->Qspid.pQspiHw;
if (channel != pArg->TxChNum)
return;
/* Release the semaphore */
ReleaseMutex(pArg->progress);
QSPI_EndTransfer(pQspiHw);
while(!QSPI_GetStatus(pArg->Qspid.pQspiHw, IsEofInst ));
memory_sync();
}
/**
* \brief QSPI xDMA Rx callback
* Invoked on SPi DMA reception done.
* \param channel DMA channel.
* \param pArg Pointer to callback argument - Pointer to Spid instance.
*/
static void QSPID_qspiRx_Cb(uint32_t channel, QspiDma_t* pArg)
{
Qspi *pQspiHw = pArg->Qspid.pQspiHw;
if (channel != pArg->RxChNum)
return;
/* Release the semaphore */
ReleaseMutex(pArg->progress);
QSPI_EndTransfer(pQspiHw);
while(!QSPI_GetStatus(pArg->Qspid.pQspiHw, IsEofInst ));
memory_sync();
}
/**
* \brief Configures the DMA for QSPI
*
* \param pQspidma Pointer to QSPI DMA structure
* \param Addr Address to Read or write of QSPI flash memory
* \param pBuffer Pointer input/output buffer
* \param ReadWrite Read or write memory flag
* \returns 0 if the dma multibuffer configuration successfully; otherwise returns
* QSPID_ERROR_XXX.
*/
static uint8_t QSPID_configureQpsiDma(QspiDma_t *pQspidma, uint32_t Addr,
QspiBuffer_t *pBuffer, Access_t const ReadWrite)
{
sXdmadCfg xdmadCfg, xdmadRxCfg,xdmadTxCfg;
uint8_t chanNum;
uint8_t qspi_id = pQspidma->Qspid.qspiId;
Qspi *pQspiHw = pQspidma->Qspid.pQspiHw;
uint32_t xdmaCndc, xdmaInt, BurstSize, ChannelWidth;
/* Setup DMA for QSPI */
if(pQspidma->Qspid.qspiMode == QSPI_MR_SMM_SPI) {
// SPI mode
/* SPI TX DMA config */
xdmadTxCfg.mbr_sa = (uint32_t)pBuffer->pDataTx;
xdmadTxCfg.mbr_da = (uint32_t)&pQspiHw->QSPI_TDR;
xdmadTxCfg.mbr_ubc = (pBuffer->TxDataSize);
xdmadTxCfg.mbr_cfg = XDMAC_CC_TYPE_PER_TRAN |
XDMAC_CC_MBSIZE_SINGLE |
XDMAC_CC_DSYNC_MEM2PER |
XDMAC_CC_CSIZE_CHK_1 |
XDMAC_CC_DWIDTH_BYTE|
XDMAC_CC_SIF_AHB_IF0 |
XDMAC_CC_DIF_AHB_IF1 |
XDMAC_CC_SAM_INCREMENTED_AM |
XDMAC_CC_DAM_FIXED_AM |
XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber
( qspi_id, XDMAD_TRANSFER_TX ));
xdmadTxCfg.mbr_bc = 0;
xdmadTxCfg.mbr_sus = 0;
xdmadTxCfg.mbr_dus =0;
/* SPI RX DMA config */
xdmadRxCfg.mbr_da = (uint32_t)pBuffer->pDataRx;
xdmadRxCfg.mbr_sa = (uint32_t)&pQspiHw->QSPI_RDR;
xdmadRxCfg.mbr_ubc = (pBuffer->RxDataSize);
xdmadRxCfg.mbr_cfg = XDMAC_CC_TYPE_PER_TRAN |
XDMAC_CC_MBSIZE_SINGLE |
XDMAC_CC_DSYNC_PER2MEM |
XDMAC_CC_CSIZE_CHK_1 |
XDMAC_CC_DWIDTH_BYTE|
XDMAC_CC_SIF_AHB_IF1 |
XDMAC_CC_DIF_AHB_IF0 |
XDMAC_CC_SAM_FIXED_AM |
XDMAC_CC_DAM_INCREMENTED_AM |
XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber
( qspi_id, XDMAD_TRANSFER_RX ));
xdmadRxCfg.mbr_bc = 0;
xdmadRxCfg.mbr_sus = 0;
xdmadRxCfg.mbr_dus =0;
xdmaCndc = 0;
/* Put all interrupts on for non LLI list setup of DMA */
xdmaInt = (XDMAC_CIE_BIE |
XDMAC_CIE_RBIE |
XDMAC_CIE_WBIE |
XDMAC_CIE_ROIE);
memory_barrier();
if (XDMAD_ConfigureTransfer
( pQspidma->pXdmad, pQspidma->RxChNum, &xdmadRxCfg, xdmaCndc, 0, xdmaInt))
return QSPID_ERROR;
if (XDMAD_ConfigureTransfer
( pQspidma->pXdmad, pQspidma->TxChNum, &xdmadTxCfg, xdmaCndc, 0, xdmaInt))
return QSPID_ERROR;
return 0;
} else {
if(ReadWrite == WriteAccess) {
xdmadCfg.mbr_sa = (uint32_t)pBuffer->pDataTx;
xdmadCfg.mbr_da = (uint32_t)( QSPIMEM_ADDR | Addr);
xdmadCfg.mbr_ubc = (pBuffer->TxDataSize);
chanNum = pQspidma->TxChNum;
ChannelWidth = XDMAC_CC_DWIDTH_BYTE;
BurstSize = XDMAC_CC_MBSIZE_SIXTEEN;
} else if(ReadWrite == ReadAccess) {
xdmadCfg.mbr_da = (uint32_t)pBuffer->pDataRx;
xdmadCfg.mbr_sa = (uint32_t)( QSPIMEM_ADDR | Addr);
xdmadCfg.mbr_ubc = ((pBuffer->RxDataSize>>2) + 1);
chanNum = pQspidma->RxChNum;
ChannelWidth = XDMAC_CC_DWIDTH_WORD;
BurstSize = XDMAC_CC_MBSIZE_SIXTEEN;
} else {
TRACE_ERROR(" QSPI error \n\r");
return 1;
}
xdmadCfg.mbr_cfg = XDMAC_CC_TYPE_MEM_TRAN |
XDMAC_CC_MEMSET_NORMAL_MODE |
BurstSize |
ChannelWidth |
XDMAC_CC_SIF_AHB_IF1 |
XDMAC_CC_DIF_AHB_IF1 |
XDMAC_CC_SAM_INCREMENTED_AM |
XDMAC_CC_DAM_INCREMENTED_AM ;
xdmadCfg.mbr_bc = 0;
xdmadCfg.mbr_sus = 0;
xdmadCfg.mbr_dus =0;
xdmaCndc = 0;
/* Put all interrupts on for non LLI list setup of DMA */
xdmaInt = (XDMAC_CIE_BIE |
XDMAC_CIE_RBIE |
XDMAC_CIE_WBIE |
XDMAC_CIE_ROIE);
memory_barrier();
if (XDMAD_ConfigureTransfer( pQspidma->pXdmad, chanNum, &xdmadCfg, xdmaCndc, 0, xdmaInt))
return QSPID_ERROR;
return 0;
}
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
/**
* \brief Initializes the pQspidma structure and the corresponding QSPI & DMA .
* hardware select value.
*
* \param pQspidma Pointer to a QspiDma_t instance.
* \param Mode Associated SPI peripheral.
* \param dwConf QSPI peripheral configuration.
* \param pXdmad Pointer to a Xdmad instance.
*/
uint32_t QSPID_Configure( QspiDma_t *pQspidma, QspiMode_t Mode,
uint32_t dwConf, sXdmad* pXdmad)
{
/* Initialize the QSPI structure */
QSPI_ConfigureInterface(&pQspidma->Qspid, Mode, dwConf);
pQspidma->Qspid.qspiCommand.Instruction = 0;
pQspidma->Qspid.qspiCommand.Option = 0;
pQspidma->RxChNum = QSPID_CH_NOT_ENABLED;
pQspidma->TxChNum = QSPID_CH_NOT_ENABLED;
pQspidma->pXdmad = pXdmad;
/* XDMA Driver initialize */
XDMAD_Initialize( pQspidma->pXdmad, 0 );
/* Configure and enable interrupt */
NVIC_ClearPendingIRQ(XDMAC_IRQn);
NVIC_SetPriority( XDMAC_IRQn ,1);
NVIC_EnableIRQ(XDMAC_IRQn);
return QSPI_SUCCESS;
}
/**
* \brief Enables a QSPI Rx channel. This function will allocate a dma Rx
* channel for QSPI
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is not
* valid.
*/
uint32_t QSPID_EnableQspiRxChannel(QspiDma_t *pQspidma)
{
static uint16_t DmaChannel;
/* Try to get the semaphore */
if (pQspidma->RxChNum != QSPID_CH_NOT_ENABLED) {
return QSPID_ERROR_LOCK;
}
/* Allocate a DMA channel */
DmaChannel = XDMAD_AllocateChannel(
pQspidma->pXdmad, XDMAD_TRANSFER_MEMORY, XDMAD_TRANSFER_MEMORY);
if ( DmaChannel == XDMAD_ALLOC_FAILED ){
return QSPID_ERROR;
}
pQspidma->RxChNum = DmaChannel;
/* Setup callbacks*/
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->RxChNum,
(XdmadTransferCallback)QSPID_qspiRx_Cb, pQspidma);
if (XDMAD_PrepareChannel( pQspidma->pXdmad, pQspidma->RxChNum ))
return QSPID_ERROR;
return 0;
}
/**
* \brief Enables a QSPI Tx channel. This function will allocate a dma Tx
* channel for QSPI
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_EnableQspiTxChannel(QspiDma_t *pQspidma)
{
static uint16_t DmaChannel;
/* Try to get the semaphore */
if (pQspidma->TxChNum != QSPID_CH_NOT_ENABLED) {
return QSPID_ERROR_LOCK;
}
/* Allocate a DMA channel */
DmaChannel = XDMAD_AllocateChannel( pQspidma->pXdmad,
XDMAD_TRANSFER_MEMORY, XDMAD_TRANSFER_MEMORY);
if ( DmaChannel == XDMAD_ALLOC_FAILED ) {
return QSPID_ERROR;
}
pQspidma->TxChNum = DmaChannel;
/* Setup callbacks */
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->TxChNum,
(XdmadTransferCallback)QSPID_qspiTx_Cb, pQspidma);
if (XDMAD_PrepareChannel( pQspidma->pXdmad, pQspidma->TxChNum ))
return QSPID_ERROR;
return 0;
}
/**
* \brief Enables a QSPI SPI Rx channel. This function will allocate a dma
* Rx channel for QSPI SPI mode
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_EnableSpiChannel(QspiDma_t *pQspidma)
{
static uint16_t DmaChannel;
/* Try to get the semaphore */
if (pQspidma->RxChNum != QSPID_CH_NOT_ENABLED) {
return QSPID_ERROR_LOCK;
}
/* Try to get the semaphore */
if (pQspidma->TxChNum != QSPID_CH_NOT_ENABLED) {
return QSPID_ERROR_LOCK;
}
/* Allocate a DMA channel */
DmaChannel = XDMAD_AllocateChannel
( pQspidma->pXdmad, pQspidma->Qspid.qspiId, XDMAD_TRANSFER_MEMORY);
if ( DmaChannel == XDMAD_ALLOC_FAILED ) {
return QSPID_ERROR;
}
pQspidma->RxChNum = DmaChannel;
/* Allocate a DMA channel */
DmaChannel = XDMAD_AllocateChannel( pQspidma->pXdmad,
XDMAD_TRANSFER_MEMORY, pQspidma->Qspid.qspiId);
if ( DmaChannel == XDMAD_ALLOC_FAILED ) {
return QSPID_ERROR;
}
pQspidma->TxChNum = DmaChannel;
/* Setup callbacks*/
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->RxChNum,
(XdmadTransferCallback)QSPID_Spi_Cb, pQspidma);
if (XDMAD_PrepareChannel( pQspidma->pXdmad, pQspidma->RxChNum ))
return QSPID_ERROR;
/* Setup callbacks for SPI0/1 TX (ignored) */
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->TxChNum, NULL, NULL);
if ( XDMAD_PrepareChannel( pQspidma->pXdmad, pQspidma->TxChNum ))
return QSPID_ERROR;
return 0;
}
/**
* \brief Disables a QSPI Rx channel. This function will de-allocate previous
* allocated dma Rx channel for QSPI
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_DisableQspiRxChannel(QspiDma_t *pQspidma)
{
XDMAC_SoftwareFlushReq(pQspidma->pXdmad->pXdmacs, pQspidma->RxChNum);
XDMAD_StopTransfer(pQspidma->pXdmad, pQspidma->RxChNum);
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->RxChNum, NULL, NULL);
/* Free allocated DMA channel for QSPI RX. */
XDMAD_FreeChannel( pQspidma->pXdmad, pQspidma->RxChNum);
pQspidma->RxChNum = QSPID_CH_NOT_ENABLED;
return 0;
}
/**
* \brief Disables a QSPI Tx channel. This function will de-allocate previous
* allocated dma Tx channel for QSPI
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_DisableQspiTxChannel(QspiDma_t *pQspidma)
{
XDMAC_SoftwareFlushReq(pQspidma->pXdmad->pXdmacs, pQspidma->TxChNum);
XDMAD_StopTransfer(pQspidma->pXdmad, pQspidma->TxChNum);
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->TxChNum, NULL, NULL);
/* Free allocated DMA channel for QSPI TX. */
XDMAD_FreeChannel( pQspidma->pXdmad, pQspidma->TxChNum);
pQspidma->TxChNum = QSPID_CH_NOT_ENABLED;
return 0;
}
/**
* \brief Disables a QSPI SPI Rx and Tx channels. This function will
* de-allocate privious allocated dma Rx, Txchannel for QSPI in SPI mode
*
* \param pQspidma Pointer to a Spid instance.
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_DisableSpiChannel(QspiDma_t *pQspidma)
{
XDMAC_SoftwareFlushReq(pQspidma->pXdmad->pXdmacs, pQspidma->RxChNum);
//XDMAC_SoftwareFlushReq(pQspidma->pXdmad->pXdmacs, pQspidma->TxChNum);
XDMAD_StopTransfer(pQspidma->pXdmad, pQspidma->RxChNum);
XDMAD_StopTransfer(pQspidma->pXdmad, pQspidma->TxChNum);
XDMAD_SetCallback(pQspidma->pXdmad, pQspidma->RxChNum, NULL, NULL);
/* Free allocated DMA channel for QSPI RX. */
XDMAD_FreeChannel( pQspidma->pXdmad, pQspidma->RxChNum);
XDMAD_FreeChannel( pQspidma->pXdmad, pQspidma->TxChNum);
pQspidma->RxChNum = QSPID_CH_NOT_ENABLED;
pQspidma->TxChNum = QSPID_CH_NOT_ENABLED;
return 0;
}
/**
* \brief Starts a QSPI read or write operation.
*
* \param pQspidma Pointer to a Qspid instance.
* \param ReadWrite Defines the memory access type
* \returns 0 if the transfer has been started successfully; otherwise returns
* QSPID_ERROR_LOCK is the driver is in use, or QSPID_ERROR if the command is
* not valid.
*/
uint32_t QSPID_ReadWriteQSPI(QspiDma_t *pQspidma, Access_t const ReadWrite)
{
QspiBuffer_t *pBuffer = &pQspidma->Qspid.qspiBuffer;
uint8_t chanNum;
uint32_t semTimer = 0x7FF;
//assert(pBuffer->pDataTx);
if (pQspidma->progress) {
return QSPID_ERROR_LOCK;
}
LockMutex(pQspidma->progress, semTimer);
if(ReadWrite == WriteAccess) {
chanNum = pQspidma->TxChNum;
} else if(ReadWrite == ReadAccess) {
chanNum = pQspidma->RxChNum;
} else {
TRACE_ERROR("%s QSPI Access Error\n\r", __FUNCTION__);
}
if (QSPID_configureQpsiDma
( pQspidma, pQspidma->Qspid.pQspiFrame->Addr, pBuffer, ReadWrite) )
return QSPID_ERROR_LOCK;
SCB_CleanInvalidateDCache();
/* Start DMA 0(RX) && 1(TX) */
if (XDMAD_StartTransfer( pQspidma->pXdmad,chanNum ))
return QSPID_ERROR_LOCK;
return 0;
}
/**
* \brief Starts a SPI master transfer. This is a non blocking function. It will
* return as soon as the transfer is started.
*
* \param pSpid Pointer to a Spid instance.
* \param pCommand Pointer to the SPI command to execute.
* \returns 0 if the transfer has been started successfully; otherwise returns
* SPID_ERROR_LOCK is the driver is in use, or SPID_ERROR if the command is not
* valid.
*/
uint32_t QSPID_ReadWriteSPI(QspiDma_t *pQspidma, Access_t const ReadWrite)
{
QspiBuffer_t *pBuffer = &pQspidma->Qspid.qspiBuffer;
uint32_t semTimer = 0x7FF;
assert(pBuffer->pDataRx);
assert(pBuffer->pDataTx);
/* Try to get the dataflash semaphore */
if (pQspidma->progress) {
return QSPID_ERROR_LOCK;
}
LockMutex(pQspidma->progress, semTimer);
if (QSPID_configureQpsiDma
( pQspidma, pQspidma->Qspid.pQspiFrame->Addr, pBuffer, ReadWrite) )
return QSPID_ERROR_LOCK;
SCB_CleanInvalidateDCache();
/* Start DMA 0(RX) && 1(TX) */
if (XDMAD_StartTransfer( pQspidma->pXdmad, pQspidma->RxChNum ))
return QSPID_ERROR_LOCK;
if (XDMAD_StartTransfer( pQspidma->pXdmad, pQspidma->TxChNum ))
return QSPID_ERROR_LOCK;
return 0;
}
/**
* \brief Check if the QSPI driver is busy.
*
* \param pSpid Pointer to a Spid instance.
* \returns 1 if the SPI driver is currently busy executing a command; otherwise
*/
uint32_t QSPID_IsBusy(volatile uint8_t *QspiSemaphore)
{
if( Is_LockFree(QspiSemaphore) ) {
return 1;
} else {
return 0;
}
}