/* * Copyright (c) 2016, Freescale Semiconductor, Inc. * Copyright 2016-2020 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include "fsl_spi_dma.h" /******************************************************************************* * Definitions ******************************************************************************/ /* Component ID definition, used by tools. */ #ifndef FSL_COMPONENT_ID #define FSL_COMPONENT_ID "platform.drivers.flexcomm_spi_dma" #endif /*configFlags & (uint32_t)kSPI_FrameDelay) != 0U) ? (uint32_t)kSPI_FrameDelay : 0U; *fifowr |= ((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) ? (uint32_t)kSPI_FrameAssert : 0U; } static void SpiConfigToFifoWR(spi_config_t *config, uint32_t *fifowr) { *fifowr |= ((uint32_t)SPI_DEASSERT_ALL & (~(uint32_t)SPI_DEASSERTNUM_SSEL((uint32_t)config->sselNum))); /* set width of data - range asserted at entry */ *fifowr |= SPI_FIFOWR_LEN(config->dataWidth); } static void PrepareTxLastWord(spi_transfer_t *xfer, uint32_t *txLastWord, spi_config_t *config) { if (config->dataWidth > kSPI_Data8Bits) { *txLastWord = (((uint32_t)xfer->txData[xfer->dataSize - 1U] << 8U) | (xfer->txData[xfer->dataSize - 2U])); } else { *txLastWord = xfer->txData[xfer->dataSize - 1U]; } XferToFifoWR(xfer, txLastWord); SpiConfigToFifoWR(config, txLastWord); } static void SPI_SetupDummy(SPI_Type *base, spi_dma_txdummy_t *dummy, spi_transfer_t *xfer, spi_config_t *spi_config_p) { uint32_t instance = SPI_GetInstance(base); uint32_t dummydata = (uint32_t)s_dummyData[instance]; dummydata |= (uint32_t)s_dummyData[instance] << 8U; dummy->word = dummydata; dummy->lastWord = dummydata; XferToFifoWR(xfer, &dummy->word); XferToFifoWR(xfer, &dummy->lastWord); SpiConfigToFifoWR(spi_config_p, &dummy->word); SpiConfigToFifoWR(spi_config_p, &dummy->lastWord); /* Clear the end of transfer bit for continue word transfer. */ dummy->word &= (~(uint32_t)kSPI_FrameAssert); } /*! * brief Initialize the SPI master DMA handle. * * This function initializes the SPI master DMA handle which can be used for other SPI master transactional APIs. * Usually, for a specified SPI instance, user need only call this API once to get the initialized handle. * * param base SPI peripheral base address. * param handle SPI handle pointer. * param callback User callback function called at the end of a transfer. * param userData User data for callback. * param txHandle DMA handle pointer for SPI Tx, the handle shall be static allocated by users. * param rxHandle DMA handle pointer for SPI Rx, the handle shall be static allocated by users. */ status_t SPI_MasterTransferCreateHandleDMA(SPI_Type *base, spi_dma_handle_t *handle, spi_dma_callback_t callback, void *userData, dma_handle_t *txHandle, dma_handle_t *rxHandle) { uint32_t instance; /* check 'base' */ assert(!(NULL == base)); if (NULL == base) { return kStatus_InvalidArgument; } /* check 'handle' */ assert(!(NULL == handle)); if (NULL == handle) { return kStatus_InvalidArgument; } instance = SPI_GetInstance(base); (void)memset(handle, 0, sizeof(*handle)); /* Set spi base to handle */ handle->txHandle = txHandle; handle->rxHandle = rxHandle; handle->callback = callback; handle->userData = userData; handle->instance = instance; handle->dataBytesEveryTime = DMA_MAX_TRANSFER_COUNT; /* Set SPI state to idle */ handle->state = (uint8_t)kSPI_Idle; /* Set handle to global state */ s_dmaPrivateHandle[instance].base = base; s_dmaPrivateHandle[instance].handle = handle; /* Install callback for Tx dma channel */ DMA_SetCallback(handle->txHandle, SPI_TxDMACallback, &s_dmaPrivateHandle[instance]); DMA_SetCallback(handle->rxHandle, SPI_RxDMACallback, &s_dmaPrivateHandle[instance]); return kStatus_Success; } /*! * brief Perform a non-blocking SPI transfer using DMA. * * note This interface returned immediately after transfer initiates, users should call * SPI_GetTransferStatus to poll the transfer status to check whether SPI transfer finished. * * param base SPI peripheral base address. * param handle SPI DMA handle pointer. * param xfer Pointer to dma transfer structure. * retval kStatus_Success Successfully start a transfer. * retval kStatus_InvalidArgument Input argument is invalid. * retval kStatus_SPI_Busy SPI is not idle, is running another transfer. */ status_t SPI_MasterTransferDMA(SPI_Type *base, spi_dma_handle_t *handle, spi_transfer_t *xfer) { assert(!((NULL == handle) || (NULL == xfer))); uint32_t instance; status_t result = kStatus_Success; spi_config_t *spi_config_p; uint32_t address; void *nextDesc = NULL; uint32_t firstTimeSize = 0; dma_transfer_config_t xferConfig = {0}; spi_config_p = (spi_config_t *)SPI_GetConfig(base); bool firstTimeIntFlag = false; bool lastTimeIntFlag = false; uint8_t bytesPerFrame = (uint8_t)((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))); handle->bytesPerFrame = bytesPerFrame; uint8_t lastwordBytes = 0U; if ((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) { handle->lastwordBytes = bytesPerFrame; lastwordBytes = bytesPerFrame; } else { handle->lastwordBytes = 0U; lastwordBytes = 0U; } if ((NULL == handle) || (NULL == xfer)) { return kStatus_InvalidArgument; } /* Byte size is zero. */ if (xfer->dataSize == 0U) { return kStatus_InvalidArgument; } /* cannot get instance from base address */ instance = SPI_GetInstance(base); /* Check if the device is busy */ if (handle->state == (uint8_t)kSPI_Busy) { return kStatus_SPI_Busy; } else { /* Set the dma unit by dataSize */ if (xfer->dataSize <= bytesPerFrame) { nextDesc = NULL; firstTimeSize = xfer->dataSize; firstTimeIntFlag = false; lastTimeIntFlag = true; } else if (xfer->dataSize - lastwordBytes <= handle->dataBytesEveryTime) { firstTimeSize = xfer->dataSize - lastwordBytes; if (lastwordBytes != 0U) { firstTimeIntFlag = false; lastTimeIntFlag = false; nextDesc = &s_spi_descriptor_table[instance]; } else { nextDesc = NULL; firstTimeIntFlag = true; } } else { firstTimeSize = handle->dataBytesEveryTime; nextDesc = NULL; firstTimeIntFlag = true; lastTimeIntFlag = false; } /* Clear FIFOs before transfer. */ base->FIFOCFG |= SPI_FIFOCFG_EMPTYTX_MASK | SPI_FIFOCFG_EMPTYRX_MASK; base->FIFOSTAT |= SPI_FIFOSTAT_TXERR_MASK | SPI_FIFOSTAT_RXERR_MASK; handle->state = (uint8_t)kSPI_Busy; handle->transferSize = xfer->dataSize; /* receive */ SPI_EnableRxDMA(base, true); address = (uint32_t)&base->FIFORD; if (xfer->rxData != NULL) { handle->rxEndData = xfer->rxData + xfer->dataSize; DMA_PrepareTransfer(&xferConfig, (uint32_t *)address, xfer->rxData, bytesPerFrame, firstTimeSize, kDMA_PeripheralToMemory, NULL); handle->rxNextData = xfer->rxData + firstTimeSize; } else { DMA_PrepareTransfer(&xferConfig, (uint32_t *)address, &s_rxDummy, ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_StaticToStatic, NULL); } (void)DMA_SubmitTransfer(handle->rxHandle, &xferConfig); handle->rxInProgress = true; DMA_StartTransfer(handle->rxHandle); /* transmit */ SPI_EnableTxDMA(base, true); address = (uint32_t)&base->FIFOWR; if (xfer->txData != NULL) { handle->txEndData = xfer->txData + xfer->dataSize; handle->txNextData = xfer->txData + firstTimeSize; if ((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) { PrepareTxLastWord(xfer, &s_txLastWord[instance], spi_config_p); } /* If end of tranfer function is enabled and data transfer frame is bigger then 1, use dma * descriptor to send the last data. */ if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) && ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize > 2U) : (xfer->dataSize > 1U))) { dma_xfercfg_t tmp_xfercfg; tmp_xfercfg.valid = true; tmp_xfercfg.swtrig = true; tmp_xfercfg.intA = false; tmp_xfercfg.byteWidth = 4U; tmp_xfercfg.srcInc = 0; tmp_xfercfg.dstInc = 0; tmp_xfercfg.transferCount = 1U; tmp_xfercfg.reload = false; tmp_xfercfg.clrtrig = false; tmp_xfercfg.intB = true; /* Create chained descriptor to transmit last word */ DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txLastWord[instance], (uint32_t *)address, NULL); } DMA_PrepareTransfer(&xferConfig, xfer->txData, (uint32_t *)address, bytesPerFrame, firstTimeSize, kDMA_MemoryToPeripheral, nextDesc); /* Disable interrupts for first descriptor to avoid calling callback twice. */ xferConfig.xfercfg.intA = firstTimeIntFlag; xferConfig.xfercfg.intB = lastTimeIntFlag; result = DMA_SubmitTransfer(handle->txHandle, &xferConfig); if (result != kStatus_Success) { return result; } } else { /* Setup tx dummy data. */ SPI_SetupDummy(base, &s_txDummy[instance], xfer, spi_config_p); if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) && ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize > 2U) : (xfer->dataSize > 1U))) { dma_xfercfg_t tmp_xfercfg; tmp_xfercfg.valid = true; tmp_xfercfg.swtrig = true; tmp_xfercfg.intA = true; tmp_xfercfg.byteWidth = (uint8_t)sizeof(uint32_t); tmp_xfercfg.srcInc = 0; tmp_xfercfg.dstInc = 0; tmp_xfercfg.transferCount = 1; tmp_xfercfg.reload = false; tmp_xfercfg.clrtrig = false; tmp_xfercfg.intB = false; /* Create chained descriptor to transmit last word */ DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txDummy[instance].lastWord, (uint32_t *)address, NULL); /* Use common API to setup first descriptor */ DMA_PrepareTransfer( &xferConfig, &s_txDummy[instance].word, (uint32_t *)address, ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize - 2U) : (xfer->dataSize - 1U)), kDMA_StaticToStatic, &s_spi_descriptor_table[instance]); /* Disable interrupts for first descriptor to avoid calling callback twice */ xferConfig.xfercfg.intA = false; xferConfig.xfercfg.intB = false; result = DMA_SubmitTransfer(handle->txHandle, &xferConfig); if (result != kStatus_Success) { return result; } } else { DMA_PrepareTransfer( &xferConfig, &s_txDummy[instance].word, (uint32_t *)address, ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_StaticToStatic, NULL); result = DMA_SubmitTransfer(handle->txHandle, &xferConfig); if (result != kStatus_Success) { return result; } } } handle->txInProgress = true; uint32_t tmpData = 0U; uint32_t writeAddress = (uint32_t) & (base->FIFOWR) + 2UL; XferToFifoWR(xfer, &tmpData); SpiConfigToFifoWR(spi_config_p, &tmpData); /* Setup the control info. * Halfword writes to just the control bits (offset 0xE22) doesn't push anything into the FIFO. * And the data access type of control bits must be uint16_t, byte writes or halfword writes to FIFOWR * will push the data and the current control bits into the FIFO. */ if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) && ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize == 2U) : (xfer->dataSize == 1U))) { *(uint16_t *)writeAddress = (uint16_t)(tmpData >> 16U); } else { /* Clear the SPI_FIFOWR_EOT_MASK bit when data is not the last. */ tmpData &= (~(uint32_t)kSPI_FrameAssert); *(uint16_t *)writeAddress = (uint16_t)(tmpData >> 16U); } DMA_StartTransfer(handle->txHandle); } return result; } /*! * brief Transfers a block of data using a DMA method. * * This function using polling way to do the first half transimission and using DMA way to * do the srcond half transimission, the transfer mechanism is half-duplex. * When do the second half transimission, code will return right away. When all data is transferred, * the callback function is called. * * param base SPI base pointer * param handle A pointer to the spi_master_dma_handle_t structure which stores the transfer state. * param transfer A pointer to the spi_half_duplex_transfer_t structure. * return status of status_t. */ status_t SPI_MasterHalfDuplexTransferDMA(SPI_Type *base, spi_dma_handle_t *handle, spi_half_duplex_transfer_t *xfer) { assert((xfer != NULL) && (handle != NULL)); spi_transfer_t tempXfer = {0}; status_t status; if (xfer->isTransmitFirst) { tempXfer.txData = xfer->txData; tempXfer.rxData = NULL; tempXfer.dataSize = xfer->txDataSize; } else { tempXfer.txData = NULL; tempXfer.rxData = xfer->rxData; tempXfer.dataSize = xfer->rxDataSize; } /* If the pcs pin keep assert between transmit and receive. */ if (xfer->isPcsAssertInTransfer) { tempXfer.configFlags = (xfer->configFlags) & (~(uint32_t)kSPI_FrameAssert); } else { tempXfer.configFlags = (xfer->configFlags) | (uint32_t)kSPI_FrameAssert; } status = SPI_MasterTransferBlocking(base, &tempXfer); if (status != kStatus_Success) { return status; } if (xfer->isTransmitFirst) { tempXfer.txData = NULL; tempXfer.rxData = xfer->rxData; tempXfer.dataSize = xfer->rxDataSize; } else { tempXfer.txData = xfer->txData; tempXfer.rxData = NULL; tempXfer.dataSize = xfer->txDataSize; } tempXfer.configFlags = xfer->configFlags; status = SPI_MasterTransferDMA(base, handle, &tempXfer); return status; } static void SPI_RxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode) { spi_dma_private_handle_t *privHandle = (spi_dma_private_handle_t *)userData; spi_dma_handle_t *spiHandle = privHandle->handle; SPI_Type *base = privHandle->base; status_t result = kStatus_Success; uint8_t bytesPerFrame = spiHandle->bytesPerFrame; uint32_t nextDataSize = 0; uint32_t address = (uint32_t)&base->FIFORD; if (spiHandle->rxNextData >= spiHandle->rxEndData) { /* change the state */ spiHandle->rxInProgress = false; /* All finished, call the callback */ if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false)) { spiHandle->state = (uint8_t)kSPI_Idle; if (spiHandle->callback != NULL) { (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData); } } } else { /* need transmit by DMA again */ if (spiHandle->rxEndData <= (spiHandle->dataBytesEveryTime + spiHandle->rxNextData)) { nextDataSize = (uint32_t)((uint32_t)spiHandle->rxEndData - (uint32_t)spiHandle->rxNextData); } else if (spiHandle->rxEndData > (spiHandle->dataBytesEveryTime + spiHandle->rxNextData)) { nextDataSize = spiHandle->dataBytesEveryTime; } else { /* MISRA 15.7*/ } dma_transfer_config_t xferConfig = {0}; DMA_PrepareTransfer(&xferConfig, (uint32_t *)(address), (uint8_t *)spiHandle->rxNextData, bytesPerFrame, nextDataSize, kDMA_PeripheralToMemory, NULL); spiHandle->rxNextData = (uint8_t *)(spiHandle->rxNextData + nextDataSize); result = DMA_SubmitTransfer(spiHandle->rxHandle, &xferConfig); if (result != kStatus_Success) { return; } DMA_StartTransfer(spiHandle->rxHandle); } } static void SPI_TxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode) { spi_dma_private_handle_t *privHandle = (spi_dma_private_handle_t *)userData; spi_dma_handle_t *spiHandle = privHandle->handle; SPI_Type *base = privHandle->base; status_t result = kStatus_Success; uint32_t instance = spiHandle->instance; bool thisTimeIntFlag = false; uint8_t bytesPerFrame = spiHandle->bytesPerFrame; void *nextDesc = NULL; uint32_t nextDataSize = 0U; uint8_t lastwordBytes = spiHandle->lastwordBytes; uint32_t writeAddress = (uint32_t) & (base->FIFOWR); if (spiHandle->txNextData + lastwordBytes >= spiHandle->txEndData) { spiHandle->txInProgress = false; if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false)) { spiHandle->state = (uint8_t)kSPI_Idle; if (spiHandle->callback != NULL) { (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData); } } } else { if ((uint32_t)((uint32_t)(spiHandle->txEndData)) <= (spiHandle->dataBytesEveryTime + lastwordBytes + (uint32_t)spiHandle->txNextData)) { if (lastwordBytes != 0U) { nextDesc = &s_spi_descriptor_table[instance]; thisTimeIntFlag = false; } else { thisTimeIntFlag = true; } nextDataSize = (uint32_t)((uint32_t)spiHandle->txEndData - (uint32_t)spiHandle->txNextData - lastwordBytes); } else if ((uint32_t)(spiHandle->txEndData) > (spiHandle->dataBytesEveryTime + lastwordBytes + (uint32_t)spiHandle->txNextData)) { nextDesc = NULL; nextDataSize = spiHandle->dataBytesEveryTime; thisTimeIntFlag = true; } else { /* MISRA 15.7*/ } dma_transfer_config_t xferConfig = {0}; DMA_PrepareTransfer(&xferConfig, (uint8_t *)spiHandle->txNextData, (uint32_t *)(writeAddress), bytesPerFrame, nextDataSize, kDMA_MemoryToPeripheral, nextDesc); spiHandle->txNextData = (uint8_t *)(spiHandle->txNextData + nextDataSize); xferConfig.xfercfg.intA = thisTimeIntFlag; xferConfig.xfercfg.intB = false; result = DMA_SubmitTransfer(spiHandle->txHandle, &xferConfig); if (result != kStatus_Success) { return; } DMA_StartTransfer(spiHandle->txHandle); } } /*! * brief Abort a SPI transfer using DMA. * * param base SPI peripheral base address. * param handle SPI DMA handle pointer. */ void SPI_MasterTransferAbortDMA(SPI_Type *base, spi_dma_handle_t *handle) { assert(NULL != handle); /* Stop tx transfer first */ DMA_AbortTransfer(handle->txHandle); /* Then rx transfer */ DMA_AbortTransfer(handle->rxHandle); /* Set the handle state */ handle->txInProgress = false; handle->rxInProgress = false; handle->state = (uint8_t)kSPI_Idle; } /*! * brief Gets the master DMA transfer remaining bytes. * * This function gets the master DMA transfer remaining bytes. * * param base SPI peripheral base address. * param handle A pointer to the spi_dma_handle_t structure which stores the transfer state. * param count A number of bytes transferred by the non-blocking transaction. * return status of status_t. */ status_t SPI_MasterTransferGetCountDMA(SPI_Type *base, spi_dma_handle_t *handle, size_t *count) { assert(handle != NULL); if (NULL == count) { return kStatus_InvalidArgument; } /* Catch when there is not an active transfer. */ if (handle->state != (uint8_t)kSPI_Busy) { *count = 0; return kStatus_NoTransferInProgress; } size_t bytes; bytes = DMA_GetRemainingBytes(handle->rxHandle->base, handle->rxHandle->channel); *count = handle->transferSize - bytes; return kStatus_Success; }