diff options
Diffstat (limited to 'bsps/arm/imxrt/mcux-sdk/drivers/pdm/fsl_pdm_edma.c')
-rw-r--r-- | bsps/arm/imxrt/mcux-sdk/drivers/pdm/fsl_pdm_edma.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/bsps/arm/imxrt/mcux-sdk/drivers/pdm/fsl_pdm_edma.c b/bsps/arm/imxrt/mcux-sdk/drivers/pdm/fsl_pdm_edma.c new file mode 100644 index 0000000000..3c8104b5f3 --- /dev/null +++ b/bsps/arm/imxrt/mcux-sdk/drivers/pdm/fsl_pdm_edma.c @@ -0,0 +1,459 @@ +/* + * 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) + +/*<! Structure definition for pdm_edma_private_handle_t. The structure is private. */ +typedef struct _pdm_edma_private_handle +{ + PDM_Type *base; + pdm_edma_handle_t *handle; +} pdm_edma_private_handle_t; + +/*! @brief pdm transfer state */ +enum _pdm_edma_transfer_state +{ + kPDM_Busy = 0x0U, /*!< PDM is busy */ + kPDM_Idle, /*!< Transfer is done. */ +}; + +/******************************************************************************* + * Prototypes + ******************************************************************************/ +/*! + * @brief PDM EDMA callback for receive. + * + * @param handle pointer to pdm_edma_handle_t structure which stores the transfer state. + * @param userData Parameter for user callback. + * @param done If the DMA transfer finished. + * @param tcds The TCD index. + */ +static void PDM_EDMACallback(edma_handle_t *handle, void *userData, bool done, uint32_t tcds); + +/*! + * @brief Mapping the enabled channel to a number power of 2. + * + * @param channel PDM channel number. + */ +static edma_modulo_t PDM_TransferMappingChannel(uint32_t *channel); +/******************************************************************************* + * Variables + ******************************************************************************/ + +/*! @brief pdm base address pointer */ +static PDM_Type *const s_pdmBases[] = PDM_BASE_PTRS; +/*<! Private handle only used for internally. */ +static pdm_edma_private_handle_t s_edmaPrivateHandle[ARRAY_SIZE(s_pdmBases)]; +/******************************************************************************* + * Code + ******************************************************************************/ +static edma_modulo_t PDM_TransferMappingChannel(uint32_t *channel) +{ + edma_modulo_t modulo = kEDMA_ModuloDisable; +#if FSL_FEATURE_PDM_CHANNEL_NUM == 8U + if (*channel == 2U) + { + modulo = kEDMA_Modulo8bytes; + } + else if ((*channel == 3U) || (*channel == 4U)) + { + *channel = 4U; + modulo = kEDMA_Modulo16bytes; + } + else + { + modulo = kEDMA_ModuloDisable; + } +#endif + + return modulo; +} + +static void PDM_EDMACallback(edma_handle_t *handle, void *userData, bool done, uint32_t tcds) +{ + pdm_edma_private_handle_t *privHandle = (pdm_edma_private_handle_t *)userData; + pdm_edma_handle_t *pdmHandle = privHandle->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; +} |