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