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