1 /*
2  * Copyright 2019 NXP
3  * All rights reserved.
4  *
5  * SPDX-License-Identifier: BSD-3-Clause
6  */
7 
8 #include "fsl_pdm_edma.h"
9 
10 /* Component ID definition, used by tools. */
11 #ifndef FSL_COMPONENT_ID
12 #define FSL_COMPONENT_ID "platform.drivers.pdm_edma"
13 #endif
14 
15 /*******************************************************************************
16  * Definitations
17  ******************************************************************************/
18 /* Used for edma_tcd_t size aligned */
19 #define STCD_ADDR(address) (edma_tcd_t *)(((uint32_t)(address) + sizeof(edma_tcd_t)) & ~(sizeof(edma_tcd_t) - 1U))
20 
21 /*<! Structure definition for pdm_edma_private_handle_t. The structure is private. */
22 typedef struct _pdm_edma_private_handle
23 {
24     PDM_Type *base;
25     pdm_edma_handle_t *handle;
26 } pdm_edma_private_handle_t;
27 
28 /*! @brief pdm transfer state */
29 enum _pdm_edma_transfer_state
30 {
31     kPDM_Busy = 0x0U, /*!< PDM is busy */
32     kPDM_Idle,        /*!< Transfer is done. */
33 };
34 
35 /*******************************************************************************
36  * Prototypes
37  ******************************************************************************/
38 /*!
39  * @brief PDM EDMA callback for receive.
40  *
41  * @param handle pointer to pdm_edma_handle_t structure which stores the transfer state.
42  * @param userData Parameter for user callback.
43  * @param done If the DMA transfer finished.
44  * @param tcds The TCD index.
45  */
46 static void PDM_EDMACallback(edma_handle_t *handle, void *userData, bool done, uint32_t tcds);
47 
48 /*!
49  * @brief Mapping the enabled channel to a number power of 2.
50  *
51  * @param channel PDM channel number.
52  */
53 static edma_modulo_t PDM_TransferMappingChannel(uint32_t *channel);
54 /*******************************************************************************
55  * Variables
56  ******************************************************************************/
57 
58 /*! @brief pdm base address pointer */
59 static PDM_Type *const s_pdmBases[] = PDM_BASE_PTRS;
60 /*<! Private handle only used for internally. */
61 static pdm_edma_private_handle_t s_edmaPrivateHandle[ARRAY_SIZE(s_pdmBases)];
62 /*******************************************************************************
63  * Code
64  ******************************************************************************/
PDM_TransferMappingChannel(uint32_t * channel)65 static edma_modulo_t PDM_TransferMappingChannel(uint32_t *channel)
66 {
67     edma_modulo_t modulo = kEDMA_ModuloDisable;
68 #if FSL_FEATURE_PDM_CHANNEL_NUM == 8U
69     if (*channel == 2U)
70     {
71         modulo = kEDMA_Modulo8bytes;
72     }
73     else if ((*channel == 3U) || (*channel == 4U))
74     {
75         *channel = 4U;
76         modulo   = kEDMA_Modulo16bytes;
77     }
78     else
79     {
80         modulo = kEDMA_ModuloDisable;
81     }
82 #endif
83 
84     return modulo;
85 }
86 
PDM_EDMACallback(edma_handle_t * handle,void * userData,bool done,uint32_t tcds)87 static void PDM_EDMACallback(edma_handle_t *handle, void *userData, bool done, uint32_t tcds)
88 {
89     pdm_edma_private_handle_t *privHandle = (pdm_edma_private_handle_t *)userData;
90     pdm_edma_handle_t *pdmHandle          = privHandle->handle;
91 
92     if (!(pdmHandle->isLoopTransfer))
93     {
94         (void)memset(&pdmHandle->tcd[pdmHandle->tcdDriver], 0, sizeof(edma_tcd_t));
95         pdmHandle->tcdDriver = (pdmHandle->tcdDriver + 1U) % pdmHandle->tcdNum;
96     }
97 #if defined FSL_EDMA_DRIVER_EDMA4 && FSL_EDMA_DRIVER_EDMA4
98     pdmHandle->receivedBytes +=
99         EDMA_TCD_BITER((&pdmHandle->tcd[pdmHandle->tcdDriver]), EDMA_TCD_TYPE(handle->base)) *
100         (EDMA_TCD_NBYTES((&pdmHandle->tcd[pdmHandle->tcdDriver]), EDMA_TCD_TYPE(handle->base)) & 0x3FFU);
101 #else
102     pdmHandle->receivedBytes +=
103         pdmHandle->tcd[pdmHandle->tcdDriver].BITER * (pdmHandle->tcd[pdmHandle->tcdDriver].NBYTES & 0x3FFU);
104 #endif
105     /* If finished a block, call the callback function */
106     if (pdmHandle->callback != NULL)
107     {
108         (pdmHandle->callback)(privHandle->base, pdmHandle, kStatus_PDM_Idle, pdmHandle->userData);
109     }
110 
111     pdmHandle->tcdUsedNum--;
112     /* If all data finished, just stop the transfer */
113     if ((pdmHandle->tcdUsedNum == 0U) && !(pdmHandle->isLoopTransfer))
114     {
115         /* Disable DMA enable bit */
116         PDM_EnableDMA(privHandle->base, false);
117         EDMA_AbortTransfer(handle);
118     }
119 }
120 
121 /*!
122  * brief Initializes the PDM Rx eDMA handle.
123  *
124  * This function initializes the PDM slave DMA handle, which can be used for other PDM master transactional APIs.
125  * Usually, for a specified PDM instance, call this API once to get the initialized handle.
126  *
127  * param base PDM base pointer.
128  * param handle PDM eDMA handle pointer.
129  * param base PDM peripheral base address.
130  * param callback Pointer to user callback function.
131  * param userData User parameter passed to the callback function.
132  * param dmaHandle eDMA handle pointer, this handle shall be static allocated by users.
133  */
PDM_TransferCreateHandleEDMA(PDM_Type * base,pdm_edma_handle_t * handle,pdm_edma_callback_t callback,void * userData,edma_handle_t * dmaHandle)134 void PDM_TransferCreateHandleEDMA(
135     PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_callback_t callback, void *userData, edma_handle_t *dmaHandle)
136 {
137     assert((handle != NULL) && (dmaHandle != NULL));
138 
139     uint32_t instance = PDM_GetInstance(base);
140 
141     /* Zero the handle */
142     (void)memset(handle, 0, sizeof(*handle));
143 
144     /* Set pdm base to handle */
145     handle->dmaHandle = dmaHandle;
146     handle->callback  = callback;
147     handle->userData  = userData;
148 
149     /* Set PDM state to idle */
150     handle->state = (uint32_t)kPDM_Idle;
151 
152     s_edmaPrivateHandle[instance].base   = base;
153     s_edmaPrivateHandle[instance].handle = handle;
154 
155     /* Install callback for Tx dma channel */
156     EDMA_SetCallback(dmaHandle, PDM_EDMACallback, &s_edmaPrivateHandle[instance]);
157 }
158 
159 /*!
160  * brief Initializes the multi PDM channel interleave type.
161  *
162  * This function initializes the PDM DMA handle member interleaveType, it shall be called only when application would
163  * like to use type kPDM_EDMAMultiChannelInterleavePerChannelBlock, since the default interleaveType is
164  * kPDM_EDMAMultiChannelInterleavePerChannelSample always
165  *
166  * param handle PDM eDMA handle pointer.
167  * param multiChannelInterleaveType Multi channel interleave type.
168  */
PDM_TransferSetMultiChannelInterleaveType(pdm_edma_handle_t * handle,pdm_edma_multi_channel_interleave_t multiChannelInterleaveType)169 void PDM_TransferSetMultiChannelInterleaveType(pdm_edma_handle_t *handle,
170                                                pdm_edma_multi_channel_interleave_t multiChannelInterleaveType)
171 {
172     handle->interleaveType = multiChannelInterleaveType;
173 }
174 
175 /*!
176  * brief Install EDMA descriptor memory.
177  *
178  * param handle Pointer to EDMA channel transfer handle.
179  * param tcdAddr EDMA head descriptor address.
180  * param tcdNum EDMA link descriptor address.
181  */
PDM_TransferInstallEDMATCDMemory(pdm_edma_handle_t * handle,void * tcdAddr,size_t tcdNum)182 void PDM_TransferInstallEDMATCDMemory(pdm_edma_handle_t *handle, void *tcdAddr, size_t tcdNum)
183 {
184     assert(handle != NULL);
185 
186     handle->tcd    = (edma_tcd_t *)tcdAddr;
187     handle->tcdNum = tcdNum;
188 }
189 
190 /*!
191  * brief Configures the PDM channel.
192  *
193  * param base PDM base pointer.
194  * param handle PDM eDMA handle pointer.
195  * param channel channel index.
196  * param pdmConfig pdm channel configurations.
197  */
PDM_TransferSetChannelConfigEDMA(PDM_Type * base,pdm_edma_handle_t * handle,uint32_t channel,const pdm_channel_config_t * config)198 void PDM_TransferSetChannelConfigEDMA(PDM_Type *base,
199                                       pdm_edma_handle_t *handle,
200                                       uint32_t channel,
201                                       const pdm_channel_config_t *config)
202 {
203     assert((handle != NULL) && (config != NULL));
204     assert(channel < (uint32_t)FSL_FEATURE_PDM_CHANNEL_NUM);
205 
206     /* Configure the PDM channel */
207     PDM_SetChannelConfig(base, channel, config);
208 
209     /* record end channel number */
210     handle->endChannel = (uint8_t)channel;
211     /* increase totoal enabled channel number */
212     handle->channelNums++;
213     /* increase count pre channel numbers */
214     handle->count = (uint8_t)(base->FIFO_CTRL & PDM_FIFO_CTRL_FIFOWMK_MASK);
215 }
216 
217 /*!
218  * brief Performs a non-blocking PDM receive using eDMA.
219  *
220  * note This interface returns immediately after the transfer initiates. Call
221  * the PDM_GetReceiveRemainingBytes to poll the transfer status and check whether the PDM transfer is finished.
222  *
223  * Mcaro MCUX_SDK_PDM_EDMA_PDM_ENABLE_INTERNAL can control whether PDM is enabled internally or externally.
224  *
225  * 1. Scatter gather case:
226  * This functio support dynamic scatter gather and staic scatter gather,
227  * a. for the dynamic scatter gather case:
228  * Application should call PDM_TransferReceiveEDMA function continuously to make sure new receive request is submit
229  *before the previous one finish. b. for the static scatter gather case: Application should use the link transfer
230  *feature and make sure a loop link transfer is provided, such as: code pdm_edma_transfer_t pdmXfer[2] =
231  *   {
232  *       {
233  *       .data  = s_buffer,
234  *       .dataSize = BUFFER_SIZE,
235  *       .linkTransfer = &pdmXfer[1],
236  *       },
237  *
238  *       {
239  *       .data  = &s_buffer[BUFFER_SIZE],
240  *       .dataSize = BUFFER_SIZE,
241  *       .linkTransfer = &pdmXfer[0]
242  *       },
243  *   };
244  *endcode
245  *
246  * 2. Multi channel case:
247  * This function support receive multi pdm channel data, for example, if two channel is requested,
248  * code
249  * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_0, &channelConfig);
250  * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_1, &channelConfig);
251  * PDM_TransferReceiveEDMA(DEMO_PDM, &s_pdmRxHandle_0, pdmXfer);
252  * endcode
253  * The output data will be formatted as below if handle->interleaveType =
254  *kPDM_EDMAMultiChannelInterleavePerChannelSample :
255  * -------------------------------------------------------------------------
256  * |CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL 1 | ....|
257  * -------------------------------------------------------------------------
258  *
259  * The output data will be formatted as below if handle->interleaveType = kPDM_EDMAMultiChannelInterleavePerChannelBlock
260  *:
261  * ----------------------------------------------------------------------------------------------------------------------
262  * |CHANNEL3 | CHANNEL3 | CHANNEL3 | .... | CHANNEL4 | CHANNEL 4 | CHANNEL4 |....| CHANNEL5 | CHANNEL 5 | CHANNEL5
263  *|....|
264  * ----------------------------------------------------------------------------------------------------------------------
265  * Note: the dataSize of xfer is the total data size, while application using
266  * kPDM_EDMAMultiChannelInterleavePerChannelBlock, the buffer size for each PDM channel is channelSize = dataSize /
267  * channelNums, there are limitation for this feature,
268  * 1. For 3 DMIC array: the dataSize shall be 4 * (channelSize)
269  * The addtional buffer is mandantory for edma modulo feature.
270  * 2. The kPDM_EDMAMultiChannelInterleavePerChannelBlock feature support below dmic array only,
271  *    2 DMIC array: CHANNEL3, CHANNEL4
272  *    3 DMIC array: CHANNEL3, CHANNEL4, CHANNEL5
273  *    4 DMIC array: CHANNEL3, CHANNEL4, CHANNEL5, CHANNEL6
274  * Any other combinations is not support, that is to SAY, THE FEATURE SUPPORT RECEIVE START FROM CHANNEL3 ONLY AND 4
275  * MAXIMUM DMIC CHANNELS.
276  *
277  * param base PDM base pointer
278  * param handle PDM eDMA handle pointer.
279  * param xfer Pointer to DMA transfer structure.
280  * retval kStatus_Success Start a PDM eDMA receive successfully.
281  * retval kStatus_InvalidArgument The input argument is invalid.
282  * retval kStatus_RxBusy PDM is busy receiving data.
283  */
PDM_TransferReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle,pdm_edma_transfer_t * xfer)284 status_t PDM_TransferReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_transfer_t *xfer)
285 {
286     assert((handle != NULL) && (xfer != NULL));
287 
288     edma_transfer_config_t config = {0};
289     uint32_t startAddr            = PDM_GetDataRegisterAddress(base, handle->endChannel - (handle->channelNums - 1UL));
290     pdm_edma_transfer_t *currentTransfer = xfer;
291     uint32_t nextTcdIndex = 0U, tcdIndex = handle->tcdUser, destOffset = FSL_FEATURE_PDM_FIFO_WIDTH;
292     uint32_t mappedChannel = handle->channelNums;
293     edma_modulo_t modulo   = kEDMA_ModuloDisable;
294     /* minor offset used for channel sample interleave transfer */
295     edma_minor_offset_config_t minorOffset = {
296         .enableSrcMinorOffset  = true,
297         .enableDestMinorOffset = false,
298         .minorOffset           = 0xFFFFFU - mappedChannel * (uint32_t)FSL_FEATURE_PDM_FIFO_OFFSET + 1U};
299 
300     /* Check if input parameter invalid */
301     if ((xfer->data == NULL) || (xfer->dataSize == 0U))
302     {
303         return kStatus_InvalidArgument;
304     }
305 
306     if ((handle->interleaveType == kPDM_EDMAMultiChannelInterleavePerChannelBlock) && (mappedChannel > 1U))
307     {
308         /* Limitation of the feature, reference the API comments */
309         if (((startAddr & 0xFU) != 0U) || (mappedChannel > 4U))
310         {
311             return kStatus_InvalidArgument;
312         }
313         modulo = PDM_TransferMappingChannel(&mappedChannel);
314         if ((xfer->dataSize % mappedChannel) != 0U)
315         {
316             return kStatus_InvalidArgument;
317         }
318         destOffset = xfer->dataSize / mappedChannel;
319         /* reconfigure the minor loop offset for channel block interleave */
320         minorOffset.enableSrcMinorOffset = false, minorOffset.enableDestMinorOffset = true,
321         minorOffset.minorOffset =
322             0xFFFFFU - mappedChannel * (uint32_t)destOffset + (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH + 1U;
323     }
324 
325     while (currentTransfer != NULL)
326     {
327         if (handle->tcdUsedNum >= handle->tcdNum)
328         {
329             return kStatus_PDM_QueueFull;
330         }
331         else
332         {
333             uint32_t primask = DisableGlobalIRQ();
334             handle->tcdUsedNum++;
335             EnableGlobalIRQ(primask);
336         }
337 
338         nextTcdIndex = (handle->tcdUser + 1U) % handle->tcdNum;
339 
340         if (mappedChannel == 1U)
341         {
342             EDMA_PrepareTransferConfig(&config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH, 0,
343                                        (uint8_t *)(uint32_t)currentTransfer->data, FSL_FEATURE_PDM_FIFO_WIDTH,
344                                        FSL_FEATURE_PDM_FIFO_WIDTH, handle->count * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH,
345                                        currentTransfer->dataSize);
346         }
347         else
348         {
349             EDMA_PrepareTransferConfig(&config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH,
350                                        FSL_FEATURE_PDM_FIFO_OFFSET, (uint8_t *)(uint32_t)currentTransfer->data,
351                                        FSL_FEATURE_PDM_FIFO_WIDTH, (int16_t)destOffset,
352                                        mappedChannel * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH, currentTransfer->dataSize);
353         }
354 #if defined FSL_EDMA_DRIVER_EDMA4 && FSL_EDMA_DRIVER_EDMA4
355         EDMA_TcdSetTransferConfigExt(handle->dmaHandle->base, (edma_tcd_t *)&handle->tcd[handle->tcdUser], &config,
356                                      (edma_tcd_t *)&handle->tcd[nextTcdIndex]);
357 
358         if (mappedChannel > 1U)
359         {
360             EDMA_TcdSetMinorOffsetConfigExt(handle->dmaHandle->base, (edma_tcd_t *)&handle->tcd[handle->tcdUser],
361                                             &minorOffset);
362 
363             if (handle->interleaveType == kPDM_EDMAMultiChannelInterleavePerChannelBlock)
364             {
365                 EDMA_TcdSetModuloExt(handle->dmaHandle->base, (edma_tcd_t *)&handle->tcd[handle->tcdUser], modulo,
366                                      kEDMA_ModuloDisable);
367             }
368         }
369 
370         EDMA_TcdEnableInterruptsExt(handle->dmaHandle->base, (edma_tcd_t *)&handle->tcd[handle->tcdUser],
371                                     (uint32_t)kEDMA_MajorInterruptEnable);
372 #else
373         EDMA_TcdSetTransferConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &config,
374                                   (edma_tcd_t *)&handle->tcd[nextTcdIndex]);
375 
376         if (mappedChannel > 1U)
377         {
378             EDMA_TcdSetMinorOffsetConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &minorOffset);
379 
380             if (handle->interleaveType == kPDM_EDMAMultiChannelInterleavePerChannelBlock)
381             {
382                 EDMA_TcdSetModulo((edma_tcd_t *)&handle->tcd[handle->tcdUser], modulo, kEDMA_ModuloDisable);
383             }
384         }
385 
386         EDMA_TcdEnableInterrupts((edma_tcd_t *)&handle->tcd[handle->tcdUser], (uint32_t)kEDMA_MajorInterruptEnable);
387 #endif
388 
389         handle->tcdUser = nextTcdIndex;
390 
391         currentTransfer = currentTransfer->linkTransfer;
392 
393         if (currentTransfer == xfer)
394         {
395             handle->isLoopTransfer = true;
396             break;
397         }
398     }
399 
400     if (handle->state != (uint32_t)kPDM_Busy)
401     {
402         EDMA_InstallTCD(handle->dmaHandle->base, handle->dmaHandle->channel, (edma_tcd_t *)&handle->tcd[tcdIndex]);
403         /* Start DMA transfer */
404         EDMA_StartTransfer(handle->dmaHandle);
405 
406         /* Enable DMA enable bit */
407         PDM_EnableDMA(base, true);
408 #if defined(MCUX_SDK_PDM_EDMA_PDM_ENABLE_INTERNAL) && MCUX_SDK_PDM_EDMA_PDM_ENABLE_INTERNAL
409         /* enable PDM */
410         PDM_Enable(base, true);
411 #endif
412 
413         handle->state = (uint32_t)kPDM_Busy;
414     }
415 
416     return kStatus_Success;
417 }
418 
419 /*!
420  * brief Aborts a PDM receive using eDMA.
421  *
422  * This function only aborts the current transfer slots, the other transfer slots' information still kept
423  * in the handler. If users want to terminate all transfer slots, just call PDM_TransferTerminateReceiveEDMA.
424  *
425  * param base PDM base pointer
426  * param handle PDM eDMA handle pointer.
427  */
PDM_TransferAbortReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle)428 void PDM_TransferAbortReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle)
429 {
430     assert(handle != NULL);
431 
432     /* Disable dma */
433     EDMA_AbortTransfer(handle->dmaHandle);
434 
435     /* Disable DMA enable bit */
436     PDM_EnableDMA(base, false);
437 
438     /* Disable PDM */
439     PDM_Enable(base, false);
440 
441     /* Handle the queue index */
442     handle->tcdUsedNum--;
443 
444     /* Set the handle state */
445     handle->state = (uint32_t)kPDM_Idle;
446 }
447 
448 /*!
449  * brief Terminate all PDM receive.
450  *
451  * This function will clear all transfer slots buffered in the pdm queue. If users only want to abort the
452  * current transfer slot, please call PDM_TransferAbortReceiveEDMA.
453  *
454  * param base PDM base pointer.
455  * param handle PDM eDMA handle pointer.
456  */
PDM_TransferTerminateReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle)457 void PDM_TransferTerminateReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle)
458 {
459     assert(handle != NULL);
460 
461     /* Abort the current transfer */
462     PDM_TransferAbortReceiveEDMA(base, handle);
463 
464     /* Clear all the internal information */
465     (void)memset(handle->tcd, 0, sizeof(edma_tcd_t) * handle->tcdNum);
466     handle->tcdUser    = 0U;
467     handle->tcdUsedNum = 0U;
468 }
469 
470 /*!
471  * brief Gets byte count received by PDM.
472  *
473  * param base PDM base pointer
474  * param handle PDM eDMA handle pointer.
475  * param count Bytes count received by PDM.
476  * retval kStatus_Success Succeed get the transfer count.
477  * retval kStatus_NoTransferInProgress There is no non-blocking transaction in progress.
478  */
PDM_TransferGetReceiveCountEDMA(PDM_Type * base,pdm_edma_handle_t * handle,size_t * count)479 status_t PDM_TransferGetReceiveCountEDMA(PDM_Type *base, pdm_edma_handle_t *handle, size_t *count)
480 {
481     assert(handle != NULL);
482 
483     *count = handle->receivedBytes;
484 
485     return kStatus_Success;
486 }
487