/* * Copyright 2017 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.vspi_dma" #endif /*configFlags & (uint32_t)kSPI_FrameDelay) ? (uint32_t)kSPI_FrameDelay : 0; *fifowr |= (xfer->configFlags & (uint32_t)kSPI_FrameAssert) ? (uint32_t)kSPI_FrameAssert : 0; *fifowr |= (xfer->configFlags & (uint32_t)kSPI_ReceiveIgnore) ? (uint32_t)kSPI_ReceiveIgnore : 0; } static void SpiConfigToFifoWR(spi_config_t *config, uint32_t *fifowr) { *fifowr |= (SPI_DEASSERT_ALL & (~SPI_DEASSERT_SSELNUM(config->sselNum))); /* set width of data - range asserted at entry */ *fifowr |= SPI_TXDATCTL_LEN(config->dataWidth); } 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); dummy->word = ((uint32_t)s_dummyData[instance] << 8U) | s_dummyData[instance]; XferToFifoWR(xfer, &dummy->word); SpiConfigToFifoWR(spi_config_p, &dummy->word); if ((xfer->configFlags & kSPI_FrameAssert) && ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize > 2) : (xfer->dataSize > 1))) { dummy->lastWord = ((uint32_t)s_dummyData[instance] << 8U) | s_dummyData[instance]; XferToFifoWR(xfer, &dummy->lastWord); SpiConfigToFifoWR(spi_config_p, &dummy->lastWord); 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) { int32_t instance = 0; /* 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); memset(handle, 0, sizeof(*handle)); /* Set spi base to handle */ handle->txHandle = txHandle; handle->rxHandle = rxHandle; handle->callback = callback; handle->userData = userData; /* Set SPI state to idle */ handle->state = 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) { int32_t instance; status_t result = kStatus_Success; spi_config_t *spi_config_p; assert(!((NULL == handle) || (NULL == xfer))); if ((NULL == handle) || (NULL == xfer)) { return kStatus_InvalidArgument; } /* byte size is zero. */ assert(!(xfer->dataSize == 0)); if (xfer->dataSize == 0) { return kStatus_InvalidArgument; } /* cannot get instance from base address */ instance = SPI_GetInstance(base); assert(!(instance < 0)); if (instance < 0) { return kStatus_InvalidArgument; } /* Check if the device is busy */ if (handle->state == kSPI_Busy) { return kStatus_SPI_Busy; } else { uint32_t tmp; dma_transfer_config_t xferConfig = {0}; spi_config_p = (spi_config_t *)SPI_GetConfig(base); handle->state = kStatus_SPI_Busy; handle->transferSize = xfer->dataSize; /* Receive */ if (xfer->rxData) { if (SPI_IsRxFifoEnabled(base)) { DMA_PrepareTransfer(&xferConfig, ((void *)((uint32_t)&VFIFO->SPI[instance].RXDATSPI)), xfer->rxData, ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_PeripheralToMemory, NULL); } else { DMA_PrepareTransfer(&xferConfig, ((void *)((uint32_t)&base->RXDAT)), xfer->rxData, ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_PeripheralToMemory, NULL); } } else { if (SPI_IsRxFifoEnabled(base)) { DMA_PrepareTransfer(&xferConfig, ((void *)((uint32_t)&VFIFO->SPI[instance].RXDATSPI)), &s_rxDummy, ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_StaticToStatic, NULL); } else { DMA_PrepareTransfer(&xferConfig, ((void *)((uint32_t)&base->RXDAT)), &s_rxDummy, ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), xfer->dataSize, kDMA_StaticToStatic, NULL); } } DMA_SubmitTransfer(handle->rxHandle, &xferConfig); handle->rxInProgress = true; DMA_StartTransfer(handle->rxHandle); /* Transmit */ tmp = 0; XferToFifoWR(xfer, &tmp); SpiConfigToFifoWR(spi_config_p, &tmp); if ((xfer->configFlags & kSPI_FrameAssert) && ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize > 2) : (xfer->dataSize > 1))) { if (spi_config_p->dataWidth > 7U) { s_txLastData[instance] = tmp | ((uint32_t)(xfer->txData[xfer->dataSize - 1]) << 8U) | (xfer->txData[xfer->dataSize - 2]); } else { s_txLastData[instance] = tmp | (xfer->txData[xfer->dataSize - 1]); } /* If not the last data, clear the end of transfer control bit. */ tmp &= ~((uint32_t)(kSPI_FrameAssert)); } if (xfer->txData) { if (SPI_IsTxFifoEnabled(base)) { if ((xfer->configFlags & kSPI_FrameAssert) && ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize > 2) : (xfer->dataSize > 1))) { dma_xfercfg_t tmp_xfercfg = {0}; tmp_xfercfg.valid = true; tmp_xfercfg.swtrig = true; tmp_xfercfg.intA = true; tmp_xfercfg.byteWidth = sizeof(uint32_t); tmp_xfercfg.srcInc = 0; tmp_xfercfg.dstInc = 0; tmp_xfercfg.transferCount = 1; /* create chained descriptor to transmit last word */ DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txLastData[instance], (void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI), NULL); if (spi_config_p->dataWidth > 7U) { DMA_PrepareTransfer(&xferConfig, (xfer->txData), ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), sizeof(uint16_t), (xfer->dataSize - 2), kDMA_MemoryToPeripheral, &s_spi_descriptor_table[instance]); } else { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), sizeof(uint8_t), (xfer->dataSize - 1), kDMA_MemoryToPeripheral, &s_spi_descriptor_table[instance]); } /* Disable interrupts for first descriptor to avoid calling callback twice */ xferConfig.xfercfg.intA = false; xferConfig.xfercfg.intB = false; } else { if (spi_config_p->dataWidth > 7U) { DMA_PrepareTransfer(&xferConfig, (xfer->txData), ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), sizeof(uint16_t), (xfer->dataSize), kDMA_MemoryToPeripheral, NULL); } else { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), sizeof(uint8_t), (xfer->dataSize), kDMA_MemoryToPeripheral, NULL); } } } else { if ((xfer->configFlags & kSPI_FrameAssert) && ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize > 2) : (xfer->dataSize > 1))) { dma_xfercfg_t tmp_xfercfg = {0}; tmp_xfercfg.valid = true; tmp_xfercfg.swtrig = true; tmp_xfercfg.intA = true; tmp_xfercfg.byteWidth = sizeof(uint32_t); tmp_xfercfg.srcInc = 0; tmp_xfercfg.dstInc = 0; tmp_xfercfg.transferCount = 1; /* Create chained descriptor to transmit last word */ DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txLastData[instance], (void *)((uint32_t)&base->TXDATCTL), NULL); if (spi_config_p->dataWidth > 7U) { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&base->TXDAT)), sizeof(uint16_t), (xfer->dataSize - 2), kDMA_MemoryToPeripheral, &s_spi_descriptor_table[instance]); } else { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&base->TXDAT)), sizeof(uint8_t), (xfer->dataSize - 1), kDMA_MemoryToPeripheral, &s_spi_descriptor_table[instance]); } /* disable interrupts for first descriptor to avoid calling callback twice */ xferConfig.xfercfg.intA = false; xferConfig.xfercfg.intB = false; } else { if (spi_config_p->dataWidth > 7U) { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&base->TXDAT)), sizeof(uint16_t), (xfer->dataSize), kDMA_MemoryToPeripheral, NULL); } else { DMA_PrepareTransfer(&xferConfig, xfer->txData, ((void *)((uint32_t)&base->TXDAT)), sizeof(uint8_t), (xfer->dataSize), kDMA_MemoryToPeripheral, NULL); } } } result = DMA_SubmitTransfer(handle->txHandle, &xferConfig); if (result != kStatus_Success) { return result; } } else { /* Create chained descriptor to transmit dummy word. */ SPI_SetupDummy(base, &s_txDummy[instance], xfer, spi_config_p); if ((xfer->configFlags & kSPI_FrameAssert) && ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize > 2) : (xfer->dataSize > 1))) { dma_xfercfg_t tmp_xfercfg = {0}; tmp_xfercfg.valid = true; tmp_xfercfg.swtrig = true; tmp_xfercfg.intA = true; tmp_xfercfg.byteWidth = sizeof(uint32_t); tmp_xfercfg.srcInc = 0; tmp_xfercfg.dstInc = 0; tmp_xfercfg.transferCount = 1; if (SPI_IsTxFifoEnabled(base)) { DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txDummy[instance].lastWord, (void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI), NULL); } else { DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txDummy[instance].lastWord, (void *)((uint32_t)&base->TXDATCTL), NULL); } if (SPI_IsTxFifoEnabled(base)) { DMA_PrepareTransfer(&xferConfig, &s_txDummy[instance].word, ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize - 2) : (xfer->dataSize - 1)), kDMA_StaticToStatic, &s_spi_descriptor_table[instance]); } else { DMA_PrepareTransfer(&xferConfig, &s_txDummy[instance].word, ((void *)((uint32_t)&base->TXDATCTL)), ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), ((spi_config_p->dataWidth > 7U) ? (xfer->dataSize - 2) : (xfer->dataSize - 1)), 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 { if (SPI_IsTxFifoEnabled(base)) { DMA_PrepareTransfer(&xferConfig, &s_txDummy[instance].word, ((void *)((uint32_t)&VFIFO->SPI[instance].TXDATSPI)), ((spi_config_p->dataWidth > 7U) ? (sizeof(uint16_t)) : (sizeof(uint8_t))), (xfer->dataSize), kDMA_StaticToStatic, NULL); } else { DMA_PrepareTransfer(&xferConfig, &s_txDummy[instance].word, ((void *)((uint32_t)&base->TXDAT)), ((spi_config_p->dataWidth > 7U) ? (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; /* Setup the control information. */ if (SPI_IsTxFifoEnabled(base)) { *((uint16_t *)((uint32_t) & (VFIFO->SPI[instance].TXDATSPI)) + 1) = (tmp >> 16U); } else { base->TXCTL = tmp; } 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); assert(handle); 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) | 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; /* change the state */ spiHandle->rxInProgress = false; /* All finished, call the callback */ if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false)) { spiHandle->state = kSPI_Idle; if (spiHandle->callback) { (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData); } } } 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; /* change the state */ spiHandle->txInProgress = false; /* All finished, call the callback */ if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false)) { spiHandle->state = kSPI_Idle; if (spiHandle->callback) { (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData); } } } /*! * 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 = 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); if (!count) { return kStatus_InvalidArgument; } /* Catch when there is not an active transfer. */ if (handle->state != 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; }