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 * Variables
50 ******************************************************************************/
51
52 /*! @brief pdm base address pointer */
53 static PDM_Type *const s_pdmBases[] = PDM_BASE_PTRS;
54 /*<! Private handle only used for internally. */
55 static pdm_edma_private_handle_t s_edmaPrivateHandle[ARRAY_SIZE(s_pdmBases)];
56 /*******************************************************************************
57 * Code
58 ******************************************************************************/
PDM_EDMACallback(edma_handle_t * handle,void * userData,bool done,uint32_t tcds)59 static void PDM_EDMACallback(edma_handle_t *handle, void *userData, bool done, uint32_t tcds)
60 {
61 pdm_edma_private_handle_t *privHandle = (pdm_edma_private_handle_t *)userData;
62 pdm_edma_handle_t *pdmHandle = privHandle->handle;
63
64 if (!(pdmHandle->isLoopTransfer))
65 {
66 (void)memset(&pdmHandle->tcd[pdmHandle->tcdDriver], 0, sizeof(edma_tcd_t));
67 pdmHandle->tcdDriver = (pdmHandle->tcdDriver + 1U) % pdmHandle->tcdNum;
68 }
69
70 pdmHandle->receivedBytes +=
71 pdmHandle->tcd[pdmHandle->tcdDriver].BITER * (pdmHandle->tcd[pdmHandle->tcdDriver].NBYTES & 0x3FFU);
72
73 /* If finished a block, call the callback function */
74 if (pdmHandle->callback != NULL)
75 {
76 (pdmHandle->callback)(privHandle->base, pdmHandle, kStatus_PDM_Idle, pdmHandle->userData);
77 }
78
79 pdmHandle->tcdUsedNum--;
80 /* If all data finished, just stop the transfer */
81 if ((pdmHandle->tcdUsedNum == 0U) && !(pdmHandle->isLoopTransfer))
82 {
83 /* Disable DMA enable bit */
84 PDM_EnableDMA(privHandle->base, false);
85 EDMA_AbortTransfer(handle);
86 }
87 }
88
89 /*!
90 * brief Initializes the PDM Rx eDMA handle.
91 *
92 * This function initializes the PDM slave DMA handle, which can be used for other PDM master transactional APIs.
93 * Usually, for a specified PDM instance, call this API once to get the initialized handle.
94 *
95 * param base PDM base pointer.
96 * param handle PDM eDMA handle pointer.
97 * param base PDM peripheral base address.
98 * param callback Pointer to user callback function.
99 * param userData User parameter passed to the callback function.
100 * param dmaHandle eDMA handle pointer, this handle shall be static allocated by users.
101 */
PDM_TransferCreateHandleEDMA(PDM_Type * base,pdm_edma_handle_t * handle,pdm_edma_callback_t callback,void * userData,edma_handle_t * dmaHandle)102 void PDM_TransferCreateHandleEDMA(
103 PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_callback_t callback, void *userData, edma_handle_t *dmaHandle)
104 {
105 assert((handle != NULL) && (dmaHandle != NULL));
106
107 uint32_t instance = PDM_GetInstance(base);
108
109 /* Zero the handle */
110 (void)memset(handle, 0, sizeof(*handle));
111
112 /* Set pdm base to handle */
113 handle->dmaHandle = dmaHandle;
114 handle->callback = callback;
115 handle->userData = userData;
116
117 /* Set PDM state to idle */
118 handle->state = (uint32_t)kPDM_Idle;
119
120 s_edmaPrivateHandle[instance].base = base;
121 s_edmaPrivateHandle[instance].handle = handle;
122
123 /* Install callback for Tx dma channel */
124 EDMA_SetCallback(dmaHandle, PDM_EDMACallback, &s_edmaPrivateHandle[instance]);
125 }
126
127 /*!
128 * brief Install EDMA descriptor memory.
129 *
130 * param handle Pointer to EDMA channel transfer handle.
131 * param tcdAddr EDMA head descriptor address.
132 * param tcdNum EDMA link descriptor address.
133 */
PDM_TransferInstallEDMATCDMemory(pdm_edma_handle_t * handle,void * tcdAddr,size_t tcdNum)134 void PDM_TransferInstallEDMATCDMemory(pdm_edma_handle_t *handle, void *tcdAddr, size_t tcdNum)
135 {
136 assert(handle != NULL);
137
138 handle->tcd = (edma_tcd_t *)tcdAddr;
139 handle->tcdNum = tcdNum;
140 }
141
142 /*!
143 * brief Configures the PDM channel.
144 *
145 * param base PDM base pointer.
146 * param handle PDM eDMA handle pointer.
147 * param channel channel index.
148 * param pdmConfig pdm channel configurations.
149 */
PDM_TransferSetChannelConfigEDMA(PDM_Type * base,pdm_edma_handle_t * handle,uint32_t channel,const pdm_channel_config_t * config)150 void PDM_TransferSetChannelConfigEDMA(PDM_Type *base,
151 pdm_edma_handle_t *handle,
152 uint32_t channel,
153 const pdm_channel_config_t *config)
154 {
155 assert((handle != NULL) && (config != NULL));
156 assert(channel < (uint32_t)FSL_FEATURE_PDM_CHANNEL_NUM);
157
158 /* Configure the PDM channel */
159 PDM_SetChannelConfig(base, channel, config);
160
161 /* record end channel number */
162 handle->endChannel = (uint8_t)channel;
163 /* increase totoal enabled channel number */
164 handle->channelNums++;
165 /* increase count pre channel numbers */
166 handle->count = (uint8_t)(base->FIFO_CTRL & PDM_FIFO_CTRL_FIFOWMK_MASK);
167 }
168
169 /*!
170 * brief Performs a non-blocking PDM receive using eDMA.
171 *
172 * note This interface returns immediately after the transfer initiates. Call
173 * the PDM_GetReceiveRemainingBytes to poll the transfer status and check whether the PDM transfer is finished.
174 *
175 * 1. Scatter gather case:
176 * This functio support dynamic scatter gather and staic scatter gather,
177 * a. for the dynamic scatter gather case:
178 * Application should call PDM_TransferReceiveEDMA function continuously to make sure new receive request is submit
179 *before the previous one finish. b. for the static scatter gather case: Application should use the link transfer
180 *feature and make sure a loop link transfer is provided, such as: code pdm_edma_transfer_t pdmXfer[2] =
181 * {
182 * {
183 * .data = s_buffer,
184 * .dataSize = BUFFER_SIZE,
185 * .linkTransfer = &pdmXfer[1],
186 * },
187 *
188 * {
189 * .data = &s_buffer[BUFFER_SIZE],
190 * .dataSize = BUFFER_SIZE,
191 * .linkTransfer = &pdmXfer[0]
192 * },
193 * };
194 *endcode
195 *
196 * 2. Multi channel case:
197 * This function support receive multi pdm channel data, for example, if two channel is requested,
198 * code
199 * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_0, &channelConfig);
200 * PDM_TransferSetChannelConfigEDMA(DEMO_PDM, &s_pdmRxHandle_0, DEMO_PDM_ENABLE_CHANNEL_1, &channelConfig);
201 * PDM_TransferReceiveEDMA(DEMO_PDM, &s_pdmRxHandle_0, pdmXfer);
202 * endcode
203 *Then the output data will be formatted as:
204 * -------------------------------------------------------------------------
205 * |CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL1 | CHANNEL0 | CHANNEL 1 | ....|
206 * -------------------------------------------------------------------------
207 *
208 * param base PDM base pointer
209 * param handle PDM eDMA handle pointer.
210 * param xfer Pointer to DMA transfer structure.
211 * retval kStatus_Success Start a PDM eDMA receive successfully.
212 * retval kStatus_InvalidArgument The input argument is invalid.
213 * retval kStatus_RxBusy PDM is busy receiving data.
214 */
PDM_TransferReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle,pdm_edma_transfer_t * xfer)215 status_t PDM_TransferReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle, pdm_edma_transfer_t *xfer)
216 {
217 assert((handle != NULL) && (xfer != NULL));
218
219 edma_transfer_config_t config = {0};
220 uint32_t startAddr = PDM_GetDataRegisterAddress(base, handle->endChannel - (handle->channelNums - 1UL));
221 pdm_edma_transfer_t *currentTransfer = xfer;
222 uint32_t nextTcdIndex = 0U, tcdIndex = handle->tcdUser;
223 edma_minor_offset_config_t minorOffset = {
224 .enableSrcMinorOffset = true,
225 .enableDestMinorOffset = false,
226 .minorOffset = 0xFFFFFU - handle->channelNums * (uint32_t)FSL_FEATURE_PDM_FIFO_OFFSET + 1U};
227
228 /* Check if input parameter invalid */
229 if ((xfer->data == NULL) || (xfer->dataSize == 0U))
230 {
231 return kStatus_InvalidArgument;
232 }
233
234 while (currentTransfer != NULL)
235 {
236 if (handle->tcdUsedNum >= handle->tcdNum)
237 {
238 return kStatus_PDM_QueueFull;
239 }
240 else
241 {
242 uint32_t primask = DisableGlobalIRQ();
243 handle->tcdUsedNum++;
244 EnableGlobalIRQ(primask);
245 }
246
247 nextTcdIndex = (handle->tcdUser + 1U) % handle->tcdNum;
248
249 if (handle->channelNums == 1U)
250 {
251 EDMA_PrepareTransferConfig(&config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH, 0,
252 (uint8_t *)(uint32_t)currentTransfer->data, FSL_FEATURE_PDM_FIFO_WIDTH,
253 FSL_FEATURE_PDM_FIFO_WIDTH, handle->count * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH,
254 currentTransfer->dataSize);
255 }
256 else
257 {
258 EDMA_PrepareTransferConfig(
259 &config, (void *)(uint32_t *)startAddr, FSL_FEATURE_PDM_FIFO_WIDTH, FSL_FEATURE_PDM_FIFO_OFFSET,
260 (uint8_t *)(uint32_t)currentTransfer->data, FSL_FEATURE_PDM_FIFO_WIDTH, FSL_FEATURE_PDM_FIFO_WIDTH,
261 handle->channelNums * (uint32_t)FSL_FEATURE_PDM_FIFO_WIDTH, currentTransfer->dataSize);
262 }
263
264 EDMA_TcdSetTransferConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &config,
265 (edma_tcd_t *)&handle->tcd[nextTcdIndex]);
266
267 if (handle->channelNums > 1U)
268 {
269 EDMA_TcdSetMinorOffsetConfig((edma_tcd_t *)&handle->tcd[handle->tcdUser], &minorOffset);
270 }
271
272 EDMA_TcdEnableInterrupts((edma_tcd_t *)&handle->tcd[handle->tcdUser], (uint32_t)kEDMA_MajorInterruptEnable);
273
274 handle->tcdUser = nextTcdIndex;
275
276 currentTransfer = currentTransfer->linkTransfer;
277
278 if (currentTransfer == xfer)
279 {
280 handle->isLoopTransfer = true;
281 break;
282 }
283 }
284
285 if (handle->state != (uint32_t)kPDM_Busy)
286 {
287 EDMA_InstallTCD(handle->dmaHandle->base, handle->dmaHandle->channel, (edma_tcd_t *)&handle->tcd[tcdIndex]);
288 /* Start DMA transfer */
289 EDMA_StartTransfer(handle->dmaHandle);
290
291 /* Enable DMA enable bit */
292 PDM_EnableDMA(base, true);
293 /* enable PDM */
294 PDM_Enable(base, true);
295
296 handle->state = (uint32_t)kPDM_Busy;
297 }
298
299 return kStatus_Success;
300 }
301
302 /*!
303 * brief Aborts a PDM receive using eDMA.
304 *
305 * This function only aborts the current transfer slots, the other transfer slots' information still kept
306 * in the handler. If users want to terminate all transfer slots, just call PDM_TransferTerminateReceiveEDMA.
307 *
308 * param base PDM base pointer
309 * param handle PDM eDMA handle pointer.
310 */
PDM_TransferAbortReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle)311 void PDM_TransferAbortReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle)
312 {
313 assert(handle != NULL);
314
315 /* Disable dma */
316 EDMA_AbortTransfer(handle->dmaHandle);
317
318 /* Disable DMA enable bit */
319 PDM_EnableDMA(base, false);
320
321 /* Disable PDM */
322 PDM_Enable(base, false);
323
324 /* Handle the queue index */
325 handle->tcdUsedNum--;
326
327 /* Set the handle state */
328 handle->state = (uint32_t)kPDM_Idle;
329 }
330
331 /*!
332 * brief Terminate all PDM receive.
333 *
334 * This function will clear all transfer slots buffered in the pdm queue. If users only want to abort the
335 * current transfer slot, please call PDM_TransferAbortReceiveEDMA.
336 *
337 * param base PDM base pointer.
338 * param handle PDM eDMA handle pointer.
339 */
PDM_TransferTerminateReceiveEDMA(PDM_Type * base,pdm_edma_handle_t * handle)340 void PDM_TransferTerminateReceiveEDMA(PDM_Type *base, pdm_edma_handle_t *handle)
341 {
342 assert(handle != NULL);
343
344 /* Abort the current transfer */
345 PDM_TransferAbortReceiveEDMA(base, handle);
346
347 /* Clear all the internal information */
348 (void)memset(handle->tcd, 0, sizeof(edma_tcd_t) * handle->tcdNum);
349 handle->tcdUser = 0U;
350 handle->tcdUsedNum = 0U;
351 }
352
353 /*!
354 * brief Gets byte count received by PDM.
355 *
356 * param base PDM base pointer
357 * param handle PDM eDMA handle pointer.
358 * param count Bytes count received by PDM.
359 * retval kStatus_Success Succeed get the transfer count.
360 * retval kStatus_NoTransferInProgress There is no non-blocking transaction in progress.
361 */
PDM_TransferGetReceiveCountEDMA(PDM_Type * base,pdm_edma_handle_t * handle,size_t * count)362 status_t PDM_TransferGetReceiveCountEDMA(PDM_Type *base, pdm_edma_handle_t *handle, size_t *count)
363 {
364 assert(handle != NULL);
365
366 *count = handle->receivedBytes;
367
368 return kStatus_Success;
369 }
370