/* ---------------------------------------------------------------------------- */
/* Atmel Microcontroller Software Support */
/* SAM Software Package License */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation */
/* */
/* All rights reserved. */
/* */
/* Redistribution and use in source and binary forms, with or without */
/* modification, are permitted provided that the following condition is 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"
#ifdef __rtems__
#include "../../../utils/utility.h"
#endif /* __rtems__ */
/*----------------------------------------------------------------------------
* 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);
SCB_InvalidateDCache_by_Addr((uint32_t *)pArg->Qspid.qspiBuffer.pDataRx,
pArg->Qspid.qspiBuffer.RxDataSize);
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));
SCB_InvalidateDCache_by_Addr((uint32_t *)pArg->Qspid.qspiBuffer.pDataRx,
pArg->Qspid.qspiBuffer.RxDataSize);
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));
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;
assert(pXdmad == &XDMAD_Instance);
pQspidma->pXdmad = pXdmad;
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 (QSPID_configureQpsiDma
(pQspidma, pQspidma->Qspid.pQspiFrame->Addr, pBuffer, ReadWrite))
return QSPID_ERROR_LOCK;
if (ReadWrite == WriteAccess) {
chanNum = pQspidma->TxChNum;
SCB_CleanDCache_by_Addr((uint32_t *)pBuffer->pDataTx, pBuffer->TxDataSize);
} else {
if (ReadWrite != ReadAccess)
TRACE_ERROR("%s QSPI Access Error\n\r", __FUNCTION__);
chanNum = pQspidma->RxChNum;
}
/* 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_CleanDCache_by_Addr((uint32_t *)pBuffer->pDataTx, pBuffer->TxDataSize);
/* 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;
}