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