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