/* * Copyright 2019 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include "fsl_pdm_edma.h" /* Component ID definition, used by tools. */ #ifndef FSL_COMPONENT_ID #define FSL_COMPONENT_ID "platform.drivers.pdm_edma" #endif /******************************************************************************* * Definitations ******************************************************************************/ /* Used for 32byte aligned */ #define STCD_ADDR(address) (edma_tcd_t *)(((uint32_t)(address) + 32) & ~0x1FU) /*handle; if (!(pdmHandle->isLoopTransfer)) { (void)memset(&pdmHandle->tcd[pdmHandle->tcdDriver], 0, sizeof(edma_tcd_t)); pdmHandle->tcdDriver = (pdmHandle->tcdDriver + 1U) % pdmHandle->tcdNum; } pdmHandle->receivedBytes += pdmHandle->tcd[pdmHandle->tcdDriver].BITER * (pdmHandle->tcd[pdmHandle->tcdDriver].NBYTES & 0x3FFU); /* If finished a block, call the callback function */ if (pdmHandle->callback != NULL) { (pdmHandle->callback)(privHandle->base, pdmHandle, kStatus_PDM_Idle, pdmHandle->userData); } pdmHandle->tcdUsedNum--; /* If all data finished, just stop the transfer */ if ((pdmHandle->tcdUsedNum == 0U) && !(pdmHandle->isLoopTransfer)) { /* Disable DMA enable bit */ PDM_EnableDMA(privHandle->base, false); EDMA_AbortTransfer(handle); } } /*! * brief Initializes the PDM Rx eDMA handle. * * This function initializes the PDM slave DMA handle, which can be used for other PDM master transactional APIs. * Usually, for a specified PDM instance, call this API once to get the initialized handle. * * param base PDM base pointer. * param handle PDM eDMA handle pointer. * param base PDM peripheral base address. * param callback Pointer to user callback function. * param userData User parameter passed to the callback function. * param dmaHandle eDMA handle pointer, this handle shall be static allocated by users. */ void PDM_TransferCreateHandleEDMA( PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_callback_t callback, void *userData, edma_handle_t *dmaHandle) { assert((handle != NULL) && (dmaHandle != NULL)); uint32_t instance = PDM_GetInstance(base); /* Zero the handle */ (void)memset(handle, 0, sizeof(*handle)); /* Set pdm base to handle */ handle->dmaHandle = dmaHandle; handle->callback = callback; handle->userData = userData; /* Set PDM state to idle */ handle->state = (uint32_t)kPDM_Idle; s_edmaPrivateHandle[instance].base = base; s_edmaPrivateHandle[instance].handle = handle; /* Install callback for Tx dma channel */ EDMA_SetCallback(dmaHandle, PDM_EDMACallback, &s_edmaPrivateHandle[instance]); } /*! * brief Initializes the multi PDM channel interleave type. * * This function initializes the PDM DMA handle member interleaveType, it shall be called only when application would * like to use type kPDM_EDMAMultiChannelInterleavePerChannelBlock, since the default interleaveType is * kPDM_EDMAMultiChannelInterleavePerChannelSample always * * param handle PDM eDMA handle pointer. * param multiChannelInterleaveType Multi channel interleave type. */ void PDM_TransferSetMultiChannelInterleaveType(pdm_edma_handle_t *handle, pdm_edma_multi_channel_interleave_t multiChannelInterleaveType) { handle->interleaveType = multiChannelInterleaveType; } /*! * brief Install EDMA descriptor memory. * * param handle Pointer to EDMA channel transfer handle. * param tcdAddr EDMA head descriptor address. * param tcdNum EDMA link descriptor address. */ void PDM_TransferInstallEDMATCDMemory(pdm_edma_handle_t *handle, void *tcdAddr, size_t tcdNum) { assert(handle != NULL); handle->tcd = (edma_tcd_t *)tcdAddr; handle->tcdNum = tcdNum; } /*! * brief Configures the PDM channel. * * param base PDM base pointer. * param handle PDM eDMA handle pointer. * param channel channel index. * param pdmConfig pdm channel configurations. */ void PDM_TransferSetChannelConfigEDMA(PDM_Type *base, pdm_edma_handle_t *handle, uint32_t channel, const pdm_channel_config_t *config) { assert((handle != NULL) && (config != NULL)); assert(channel < (uint32_t)FSL_FEATURE_PDM_CHANNEL_NUM); /* Configure the PDM channel */ PDM_SetChannelConfig(base, channel, config); /* record end channel number */ handle->endChannel = (uint8_t)channel; /* increase totoal enabled channel number */ handle->channelNums++; /* increase count pre channel numbers */ handle->count = (uint8_t)(base->FIFO_CTRL & PDM_FIFO_CTRL_FIFOWMK_MASK); } /*! * brief Performs a non-blocking PDM receive using eDMA. * * note This interface returns immediately after the transfer initiates. Call * the PDM_GetReceiveRemainingBytes to poll the transfer status and check whether the PDM transfer is finished. * * 1. Scatter gather case: * This functio support dynamic scatter gather and staic scatter gather, * a. for the dynamic scatter gather case: * Application should call PDM_TransferReceiveEDMA function continuously to make sure new receive request is submit *before the previous one finish. b. for the static scatter gather case: Application should use the link transfer *feature and make sure a loop link transfer is provided, such as: code pdm_edma_transfer_t pdmXfer[2] = * { * { * .data = s_buffer, * .dataSize = BUFFER_SIZE, * .linkTransfer = &pdmXfer[1], * }, * * { * .data = &s_buffer[BUFFER_SIZE], * .dataSize = BUFFER_SIZE, * .linkTransfer = &pdmXfer[0] * }, * }; *endcode * * 2. Multi channel case: * This function support receive multi pdm channel data, for example, if two channel is requested, * code * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_0, &channelConfig); * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_1, &channelConfig); * PDM_TransferReceiveEDMA(DEMO_PDM, &s_pdmRxHandle_0, pdmXfer); * endcode * The output data will be formatted as below if handle->interleaveType = *kPDM_EDMAMultiChannelInterleavePerChannelSample : * ------------------------------------------------------------------------- * |CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL 1 | ....| * ------------------------------------------------------------------------- * * The output data will be formatted as below if handle->interleaveType = kPDM_EDMAMultiChannelInterleavePerChannelBlock *: * ---------------------------------------------------------------------------------------------------------------------- * |CHANNEL3 | CHANNEL3 | CHANNEL3 | .... | CHANNEL4 | CHANNEL 4 | CHANNEL4 |....| CHANNEL5 | CHANNEL 5 | CHANNEL5 *|....| * ---------------------------------------------------------------------------------------------------------------------- * Note: the dataSize of xfer is the total data size, while application using * kPDM_EDMAMultiChannelInterleavePerChannelBlock, the buffer size for each PDM channel is channelSize = dataSize / * channelNums, there are limitation for this feature, * 1. For 3 DMIC array: the dataSize shall be 4 * (channelSize) * The addtional buffer is mandantory for edma modulo feature. * 2. The kPDM_EDMAMultiChannelInterleavePerChannelBlock feature support below dmic array only, * 2 DMIC array: CHANNEL3, CHANNEL4 * 3 DMIC array: CHANNEL3, CHANNEL4, CHANNEL5 * 4 DMIC array: CHANNEL3, CHANNEL4, CHANNEL5, CHANNEL6 * Any other combinations is not support, that is to SAY, THE FEATURE SUPPORT RECEIVE START FROM CHANNEL3 ONLY AND 4 * MAXIMUM DMIC CHANNELS. * * param base PDM base pointer * param handle PDM eDMA handle pointer. * param xfer Pointer to DMA transfer structure. * retval kStatus_Success Start a PDM eDMA receive successfully. * retval kStatus_InvalidArgument The input argument is invalid. * retval kStatus_RxBusy PDM is busy receiving data. */ status_t PDM_TransferReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_transfer_t *xfer) { assert((handle != NULL) && (xfer != NULL)); edma_transfer_config_t config = {0}; uint32_t startAddr = PDM_GetDataRegisterAddress(base, handle->endChannel - (handle->channelNums - 1UL)); pdm_edma_transfer_t *currentTransfer = xfer; uint32_t nextTcdIndex = 0U, tcdIndex = handle->tcdUser, destOffset = FSL_FEATURE_PDM_FIFO_WIDTH; uint32_t mappedChannel = handle->channelNums; edma_modulo_t modulo = kEDMA_ModuloDisable; /* minor offset used for channel sample interleave transfer */ edma_minor_offset_config_t minorOffset = { .enableSrcMinorOffset = true, .enableDestMinorOffset = false, .minorOffset = 0xFFFFFU - mappedChannel * (uint32_t)FSL_FEATURE_PDM_FIFO_OFFSET + 1U}; /* Check if input parameter invalid */ if ((xfer->data == NULL) || (xfer->dataSize == 0U)) { return kStatus_InvalidArgument; } if ((handle->interleaveType == kPDM_EDMAMultiChannelInterleavePerChannelBlock) && (mappedChannel > 1U)) { /* Limitation of the feature, reference the API comments */ if (((startAddr & 0xFU) != 0U) || (mappedChannel > 4U)) { return kStatus_InvalidArgument; } modulo = PDM_TransferMappingChannel(&mappedChannel); if ((xfer->dataSize % mappedChannel) != 0U) { return kStatus_InvalidArgument; } destOffset = xfer->dataSize / mappedChannel; /* reconfigure the minor loop offset for channel block interleave */ minorOffset.enableSrcMinorOffset = false, minorOffset.enableDestMinorOffset = true, minorOffset.minorOffset = 0xFFFFFU - mappedChannel * (uint32_t)destOffset + (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH + 1U; } while (currentTransfer != NULL) { if (handle->tcdUsedNum >= handle->tcdNum) { return kStatus_PDM_QueueFull; } else { uint32_t primask = DisableGlobalIRQ(); handle->tcdUsedNum++; EnableGlobalIRQ(primask); } nextTcdIndex = (handle->tcdUser + 1U) % handle->tcdNum; if (mappedChannel == 1U) { EDMA_PrepareTransferConfig(&config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH, 0, (uint8_t *)(uint32_t)currentTransfer->data, FSL_FEATURE_PDM_FIFO_WIDTH, FSL_FEATURE_PDM_FIFO_WIDTH, handle->count * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH, currentTransfer->dataSize); } else { EDMA_PrepareTransferConfig(&config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH, FSL_FEATURE_PDM_FIFO_OFFSET, (uint8_t *)(uint32_t)currentTransfer->data, FSL_FEATURE_PDM_FIFO_WIDTH, (int16_t)destOffset, mappedChannel * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH, currentTransfer->dataSize); } EDMA_TcdSetTransferConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &config, (edma_tcd_t *)&handle->tcd[nextTcdIndex]); if (mappedChannel > 1U) { EDMA_TcdSetMinorOffsetConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &minorOffset); if (handle->interleaveType == kPDM_EDMAMultiChannelInterleavePerChannelBlock) { EDMA_TcdSetModulo((edma_tcd_t *)&handle->tcd[handle->tcdUser], modulo, kEDMA_ModuloDisable); } } EDMA_TcdEnableInterrupts((edma_tcd_t *)&handle->tcd[handle->tcdUser], (uint32_t)kEDMA_MajorInterruptEnable); handle->tcdUser = nextTcdIndex; currentTransfer = currentTransfer->linkTransfer; if (currentTransfer == xfer) { handle->isLoopTransfer = true; break; } } if (handle->state != (uint32_t)kPDM_Busy) { EDMA_InstallTCD(handle->dmaHandle->base, handle->dmaHandle->channel, (edma_tcd_t *)&handle->tcd[tcdIndex]); /* Start DMA transfer */ EDMA_StartTransfer(handle->dmaHandle); /* Enable DMA enable bit */ PDM_EnableDMA(base, true); /* enable PDM */ PDM_Enable(base, true); handle->state = (uint32_t)kPDM_Busy; } return kStatus_Success; } /*! * brief Aborts a PDM receive using eDMA. * * This function only aborts the current transfer slots, the other transfer slots' information still kept * in the handler. If users want to terminate all transfer slots, just call PDM_TransferTerminateReceiveEDMA. * * param base PDM base pointer * param handle PDM eDMA handle pointer. */ void PDM_TransferAbortReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle) { assert(handle != NULL); /* Disable dma */ EDMA_AbortTransfer(handle->dmaHandle); /* Disable DMA enable bit */ PDM_EnableDMA(base, false); /* Disable PDM */ PDM_Enable(base, false); /* Handle the queue index */ handle->tcdUsedNum--; /* Set the handle state */ handle->state = (uint32_t)kPDM_Idle; } /*! * brief Terminate all PDM receive. * * This function will clear all transfer slots buffered in the pdm queue. If users only want to abort the * current transfer slot, please call PDM_TransferAbortReceiveEDMA. * * param base PDM base pointer. * param handle PDM eDMA handle pointer. */ void PDM_TransferTerminateReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle) { assert(handle != NULL); /* Abort the current transfer */ PDM_TransferAbortReceiveEDMA(base, handle); /* Clear all the internal information */ (void)memset(handle->tcd, 0, sizeof(edma_tcd_t) * handle->tcdNum); handle->tcdUser = 0U; handle->tcdUsedNum = 0U; } /*! * brief Gets byte count received by PDM. * * param base PDM base pointer * param handle PDM eDMA handle pointer. * param count Bytes count received by PDM. * retval kStatus_Success Succeed get the transfer count. * retval kStatus_NoTransferInProgress There is no non-blocking transaction in progress. */ status_t PDM_TransferGetReceiveCountEDMA(PDM_Type *base, pdm_edma_handle_t *handle, size_t *count) { assert(handle != NULL); *count = handle->receivedBytes; return kStatus_Success; }