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