1 /***************************************************************************//**
2  * @file
3  * @brief Pulse Density Modulation (PDM) peripheral API
4  *******************************************************************************
5  * # License
6  * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
7  *******************************************************************************
8  *
9  * SPDX-License-Identifier: Zlib
10  *
11  * The licensor of this software is Silicon Laboratories Inc.
12  *
13  * This software is provided 'as-is', without any express or implied
14  * warranty. In no event will the authors be held liable for any damages
15  * arising from the use of this software.
16  *
17  * Permission is granted to anyone to use this software for any purpose,
18  * including commercial applications, and to alter it and redistribute it
19  * freely, subject to the following restrictions:
20  *
21  * 1. The origin of this software must not be misrepresented; you must not
22  *    claim that you wrote the original software. If you use this software
23  *    in a product, an acknowledgment in the product documentation would be
24  *    appreciated but is not required.
25  * 2. Altered source versions must be plainly marked as such, and must not be
26  *    misrepresented as being the original software.
27  * 3. This notice may not be removed or altered from any source distribution.
28  *
29  ******************************************************************************/
30 
31 #ifndef EM_PDM_H
32 #define EM_PDM_H
33 
34 #include "em_device.h"
35 
36 #include <stdint.h>
37 #include <stdbool.h>
38 
39 #if defined(PDM_PRESENT) && (PDM_COUNT == 1)
40 
41 #ifdef __cplusplus
42 extern "C" {
43 #endif
44 
45 /* *INDENT-OFF* */
46 /***************************************************************************//**
47  * @addtogroup pdm PDM - Pulse Density Modulation
48  * @brief Pulse Density Modulation (PDM) peripheral API
49  *
50  * @details
51  * PDM API functions provide full support for the PDM peripheral.
52  * The PDM peripheral accepts PDM bitstreams and produces PCM encoded output.
53  *
54  * <b> The following is an example PDM usage when interfacing to two PDM microphones: </b>
55  *
56  * Configure clocks and GPIO pins:
57  *  @code
58     PDM_Init_TypeDef     pdmInit = PDM_INIT_DEFAULT;
59     CMU_DPLLInit_TypeDef pllInit = CMU_DPLL_LFXO_TO_40MHZ;
60 
61     CMU_OscillatorEnable(cmuOsc_LFXO, true, true);
62     // Lock PLL to 1,411,209 Hz to achieve 44,100 kHz PCM sampling rate
63     // when using 32x PDM oversampling
64     pllInit.frequency = 1411209;
65     pllInit.m = 14;
66     pllInit.n = 645;
67     CMU_DPLLLock(&pllInit);
68 
69     // Setup all GPIO's.
70     GPIO_PinModeSet(MIC_CLK_PORT, MIC_CLK_PIN, gpioModePushPull, 0);
71     GPIO_PinModeSet(MIC_DATA_PORT, MIC_DATA_PIN, gpioModeInput, 0);
72 
73     // Set fast slew rate on PDM mic CLK and DATA pins
74     GPIO_SlewrateSet(MIC_CLK_PORT, 7U, 7U);
75 
76     // Enable PDM peripheral clock.
77     CMU_ClockEnable(cmuClock_PDM, true);
78     // Select PDM reference clock source and enable it.
79     CMU_ClockSelectSet(cmuClock_PDMREF, cmuSelect_HFRCO);
80     CMU_ClockEnable(cmuClock_PDMREF, true);
81 
82     // Route PDM signals to correct GPIO's.
83     PDM->ROUTELOC0 = (PDM->ROUTELOC0 & ~_PDM_ROUTELOC0_DAT0LOC_MASK)
84                      | (MIC_DATA_PDM_LOC << _PDM_ROUTELOC0_DAT0LOC_SHIFT);
85     PDM->ROUTELOC1 = MIC_CLK_PDM_LOC << _PDM_ROUTELOC1_CLKLOC_SHIFT;
86     PDM->ROUTEPEN |= PDM_ROUTEPEN_CLKPEN | PDM_ROUTEPEN_DAT0PEN;@endcode
87  *
88  * Initialize and start PDM, then read PCM samples from FIFO:
89  *
90  *  @code
91     PDM_Init_TypeDef init = PDM_INIT_DEFAULT;
92     PDM_Init(PDM, &init);
93 
94     while (true) {
95       *pBuffer++ = PDM_Rx(PDM);
96     }@endcode
97  *
98  * @{
99  ******************************************************************************/
100 /* *INDENT-ON* */
101 
102 /*******************************************************************************
103  ********************************   ENUMS   ************************************
104  ******************************************************************************/
105 
106 #if defined(PDM_CFG0_NUMCH_THREE)
107 /** Configure CH3 CLK Polarity. */
108 typedef enum {
109   pdmCh3ClkPolarityRisingEdge  = _PDM_CFG0_CH3CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
110   pdmCh3ClkPolarityFallingEdge = _PDM_CFG0_CH3CLKPOL_INVERT  /**< Input data clocked on falling clock edge. */
111 } PDM_Ch3ClkPolarity_Typedef;
112 
113 /** Configure CH2 CLK Polarity. */
114 typedef enum {
115   pdmCh2ClkPolarityRisingEdge  = _PDM_CFG0_CH2CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
116   pdmCh2ClkPolarityFallingEdge = _PDM_CFG0_CH2CLKPOL_INVERT  /**< Input data clocked on falling clock edge. */
117 } PDM_Ch2ClkPolarity_Typedef;
118 #endif
119 
120 /** Configure CH1 CLK Polarity. */
121 typedef enum {
122   pdmCh1ClkPolarityRisingEdge  = _PDM_CFG0_CH1CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
123   pdmCh1ClkPolarityFallingEdge = _PDM_CFG0_CH1CLKPOL_INVERT  /**< Input data clocked on falling clock edge. */
124 } PDM_Ch1ClkPolarity_Typedef;
125 
126 /** Configure CH0 CLK Polarity. */
127 typedef enum {
128   pdmCh0ClkPolarityRisingEdge  = _PDM_CFG0_CH0CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
129   pdmCh0ClkPolarityFallingEdge = _PDM_CFG0_CH0CLKPOL_INVERT  /**< Input data clocked on falling clock edge. */
130 } PDM_Ch0ClkPolarity_Typedef;
131 
132 /** Configure FIFO Data valid level water-mark. */
133 typedef enum {
134   pdmFifoValidWatermarkOne   = _PDM_CFG0_FIFODVL_ONE,   /**< At least one word. */
135   pdmFifoValidWatermarkTwo   = _PDM_CFG0_FIFODVL_TWO,   /**< Two words. */
136   pdmFifoValidWatermarkThree = _PDM_CFG0_FIFODVL_THREE, /**< Three words. */
137   pdmFifoValidWatermarkFour  = _PDM_CFG0_FIFODVL_FOUR   /**< Four words. */
138 } PDM_FifoValidWatermark_Typedef;
139 
140 /** Configure PDM filter data output format. */
141 typedef enum {
142   pdmDataFormatRight16   = _PDM_CFG0_DATAFORMAT_RIGHT16,    /**< Right aligned 16-bit, left bits are sign extended. */
143   pdmDataFormatDouble16  = _PDM_CFG0_DATAFORMAT_DOUBLE16,   /**< Pack two 16-bit samples into one 32-bit word. */
144   pdmDataFormatRight24   = _PDM_CFG0_DATAFORMAT_RIGHT24,    /**< Right aligned 24bit, left bits are sign extended. */
145   pdmDataFormatFull32bit = _PDM_CFG0_DATAFORMAT_FULL32BIT,  /**< 32 bit data. */
146   pdmDataFormatLeft16    = _PDM_CFG0_DATAFORMAT_LEFT16,     /**< Left aligned 16-bit, right bits are zeros. */
147   pdmDataFormatLeft24    = _PDM_CFG0_DATAFORMAT_LEFT24,     /**< Left aligned 24-bit, right bits are zeros. */
148   pdmDataFormatRaw32bit  = _PDM_CFG0_DATAFORMAT_RAW32BIT    /**< RAW 32 bit data from integrator. */
149 } PDM_DataFormat_TypeDef;
150 
151 /** Configure number of PDM channels. */
152 typedef enum {
153   pdmNumberOfChannelsOne   = _PDM_CFG0_NUMCH_ONE,       /**< Only one Channel. */
154   pdmNumberOfChannelsTwo   = _PDM_CFG0_NUMCH_TWO,       /**< Two Channels. */
155 #if defined(PDM_CFG0_NUMCH_THREE)
156   pdmNumberOfChannelsThree = _PDM_CFG0_NUMCH_THREE,     /**< Three Channels. */
157   pdmNumberOfChannelsFour  = _PDM_CFG0_NUMCH_FOUR       /**< Four Channels. */
158 #endif
159 } PDM_NumberOfChannels_TypeDef;
160 
161 /** Configure order of the PDM filter. */
162 typedef enum {
163   pdmFilterOrderSecond = _PDM_CFG0_FORDER_SECOND,       /**< Second order filter. */
164   pdmFilterOrderThird  = _PDM_CFG0_FORDER_THIRD,        /**< Third order filter. */
165   pdmFilterOrderFourth = _PDM_CFG0_FORDER_FOURTH,       /**< Fourth order filter. */
166   pdmFilterOrderFifth  = _PDM_CFG0_FORDER_FIFTH         /**< Fifth order filter. */
167 } PDM_FilterOrder_TypeDef;
168 
169 /*******************************************************************************
170  *******************************   STRUCTS   ***********************************
171  ******************************************************************************/
172 
173 /** PDM initialization structure. */
174 typedef struct {
175   bool                            start;                /**< Start PDM filter after initialization. */
176 #if defined(PDM_CTRL_OUTCLKEN)
177   bool                            outClkEn;             /**< Enable PDM clock. */
178 #endif
179   uint32_t                        dsr;                  /**< PDM down sampling rate. */
180   uint32_t                        gain;                 /**< PDM gain. */
181 #if defined(PDM_CFG0_NUMCH_THREE)
182   PDM_Ch3ClkPolarity_Typedef      ch3ClkPolarity;       /**< Ch 3 clock polarity. */
183   PDM_Ch2ClkPolarity_Typedef      ch2ClkPolarity;       /**< Ch 2 clock polarity. */
184 #endif
185   PDM_Ch1ClkPolarity_Typedef      ch1ClkPolarity;       /**< Ch 1 clock polarity. */
186   PDM_Ch0ClkPolarity_Typedef      ch0ClkPolarity;       /**< Ch 0 clock polarity. */
187 #if defined(PDM_CFG0_NUMCH_THREE)
188   bool                            enableCh2Ch3Stereo;   /**< Enable stereo mode for channel pair CH2 and CH3. */
189 #endif
190   bool                            enableCh0Ch1Stereo;   /**< Enable stereo mode for channel pair CH0 and CH1. */
191   PDM_FifoValidWatermark_Typedef  fifoValidWatermark;   /**< FIFO Data valid level water-mark. */
192   PDM_DataFormat_TypeDef          dataFormat;           /**< PDM filter data output format. */
193   PDM_NumberOfChannels_TypeDef    numChannels;          /**< Number of PDM channels. */
194   PDM_FilterOrder_TypeDef         filterOrder;          /**< PDM filter order. */
195   uint32_t                        prescaler;            /**< PDM clock prescaler, resulting PDM clock is input clock / (prescaler + 1). */
196 } PDM_Init_TypeDef;
197 
198 /*******************************************************************************
199  **************************   STRUCT INITIALIZERS   ****************************
200  ******************************************************************************/
201 
202 /** Default configuration for PDM.
203  *  Stereo Ch0/1, 16bit samples, 44,100 Hz sampling rate,
204  *  32 times oversampling (requires 1,411,209 Hz PDM clock).
205  */
206 #if defined(PDM_CFG0_NUMCH_THREE)
207 #define PDM_INIT_DEFAULT                                                                   \
208   {                                                                                        \
209     true,                           /* Start PDM filter after initialization. */           \
210     true,                           /* Enable PDM clock. */                                \
211     32U,                            /* PDM down sampling rate. */                          \
212     5U,                             /* PDM gain. */                                        \
213     pdmCh3ClkPolarityRisingEdge,    /* N/A. */                                             \
214     pdmCh2ClkPolarityRisingEdge,    /* N/A. */                                             \
215     pdmCh1ClkPolarityFallingEdge,   /* Input data clocked on falling clock edge. */        \
216     pdmCh0ClkPolarityRisingEdge,    /* Input data clocked on rising clock edge. */         \
217     false,                          /* N/A. */                                             \
218     true,                           /* Enable stereo mode for channel pair CH0 and CH1. */ \
219     pdmFifoValidWatermarkOne,       /* At least one word water-mark level. */              \
220     pdmDataFormatDouble16,          /* Two 16-bit samples per FIFO entry. */               \
221     pdmNumberOfChannelsTwo,         /* Two Channels. */                                    \
222     pdmFilterOrderFifth,            /* Fifth order filter. */                              \
223     0U                              /* No clock prescaling. */                             \
224   }
225 #else
226 #define PDM_INIT_DEFAULT                                                                   \
227   {                                                                                        \
228     true,                           /* Start PDM filter after initialization. */           \
229     32U,                            /* PDM down sampling rate. */                          \
230     5U,                             /* PDM gain. */                                        \
231     pdmCh1ClkPolarityFallingEdge,   /* Input data clocked on falling clock edge. */        \
232     pdmCh0ClkPolarityRisingEdge,    /* Input data clocked on rising clock edge. */         \
233     true,                           /* Enable stereo mode for channel pair CH0 and CH1. */ \
234     pdmFifoValidWatermarkOne,       /* At least one word water-mark level. */              \
235     pdmDataFormatDouble16,          /* Two 16-bit samples per FIFO entry. */               \
236     pdmNumberOfChannelsTwo,         /* Two Channels. */                                    \
237     pdmFilterOrderFifth,            /* Fifth order filter. */                              \
238     0U                              /* No clock prescaling. */                             \
239   }
240 #endif
241 
242 /*******************************************************************************
243  *****************************   PROTOTYPES   **********************************
244  ******************************************************************************/
245 
246 void PDM_DeInit(PDM_TypeDef *pdm);
247 void PDM_Init(PDM_TypeDef *pdm, const PDM_Init_TypeDef *init);
248 void PDM_Reset(PDM_TypeDef *pdm);
249 
250 /***************************************************************************//**
251  * @brief
252  *   Clear the PDM filter.
253  *
254  * @param[in] pdm
255  *   A pointer to the PDM peripheral register block.
256  ******************************************************************************/
PDM_Clear(PDM_TypeDef * pdm)257 __STATIC_INLINE void PDM_Clear(PDM_TypeDef *pdm)
258 {
259   while (pdm->SYNCBUSY != 0U) {
260     // Wait for any pending CMD synchronization
261   }
262   pdm->CMD = PDM_CMD_CLEAR;
263 }
264 
265 /***************************************************************************//**
266  * @brief
267  *   Flush the PDM sample FIFO.
268  *
269  * @param[in] pdm
270  *   A pointer to the PDM peripheral register block.
271  ******************************************************************************/
PDM_FifoFlush(PDM_TypeDef * pdm)272 __STATIC_INLINE void PDM_FifoFlush(PDM_TypeDef *pdm)
273 {
274   while (pdm->SYNCBUSY != 0U) {
275     // Wait for any pending CMD synchronization
276   }
277   pdm->CMD = PDM_CMD_FIFOFL;
278 }
279 
280 /***************************************************************************//**
281  * @brief
282  *   Clear one or more pending PDM interrupts.
283  *
284  * @param[in] pdm
285  *   A pointer to the PDM peripheral register block.
286  *
287  * @param[in] flags
288  *   Pending PDM interrupt sources to clear. Use one or more valid
289  *   interrupt flags for the PDM module. The flags are PDM_IFC_DV,
290  *   PDM_IFC_DVL, PDM_IFC_OF and PDM_IFC_UF.
291  ******************************************************************************/
PDM_IntClear(PDM_TypeDef * pdm,uint32_t flags)292 __STATIC_INLINE void PDM_IntClear(PDM_TypeDef *pdm, uint32_t flags)
293 {
294 #if defined(PDM_HAS_SET_CLEAR)
295   pdm->IF_CLR = flags;
296 #else
297   pdm->IFC = flags;
298 #endif
299 }
300 
301 /***************************************************************************//**
302  * @brief
303  *   Disable one or more PDM interrupts.
304  *
305  * @param[in] pdm
306  *   A pointer to the PDM peripheral register block.
307  *
308  * @param[in] flags
309  *   PDM interrupt sources to disable. Use one or more valid
310  *   interrupt flags for the PDM module. The flags are PDM_IEN_DV,
311  *   PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
312  ******************************************************************************/
PDM_IntDisable(PDM_TypeDef * pdm,uint32_t flags)313 __STATIC_INLINE void PDM_IntDisable(PDM_TypeDef *pdm, uint32_t flags)
314 {
315   pdm->IEN &= ~flags;
316 }
317 
318 /***************************************************************************//**
319  * @brief
320  *   Enable one or more PDM interrupts.
321  *
322  * @note
323  *   Depending on the use, a pending interrupt may already be set prior to
324  *   enabling the interrupt. To ignore a pending interrupt, consider using
325  *   PDM_IntClear() prior to enabling the interrupt.
326  *
327  * @param[in] pdm
328  *   A pointer to the PDM peripheral register block.
329  *
330  * @param[in] flags
331  *   PDM interrupt sources to enable. Use one or more valid
332  *   interrupt flags for the PDM module. The flags are PDM_IEN_DV,
333  *   PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
334  ******************************************************************************/
PDM_IntEnable(PDM_TypeDef * pdm,uint32_t flags)335 __STATIC_INLINE void PDM_IntEnable(PDM_TypeDef *pdm, uint32_t flags)
336 {
337   pdm->IEN |= flags;
338 }
339 
340 /***************************************************************************//**
341  * @brief
342  *   Get pending PDM interrupt flags.
343  *
344  * @note
345  *   Event bits are not cleared by the use of this function.
346  *
347  * @param[in] pdm
348  *   A pointer to the PDM peripheral register block.
349  *
350  * @return
351  *   PDM interrupt sources pending. Returns one or more valid
352  *   interrupt flags for PDM module. The flags are PDM_IF_DV,
353  *   PDM_IF_DVL, PDM_IF_OF and PDM_IF_UF.
354  ******************************************************************************/
PDM_IntGet(PDM_TypeDef * pdm)355 __STATIC_INLINE uint32_t PDM_IntGet(PDM_TypeDef *pdm)
356 {
357   return pdm->IF;
358 }
359 
360 /***************************************************************************//**
361  * @brief
362  *   Get enabled and pending PDM interrupt flags.
363  *   Useful for handling more interrupt sources in the same interrupt handler.
364  *
365  * @note
366  *   Interrupt flags are not cleared by the use of this function.
367  *
368  * @param[in] pdm
369  *   A pointer to the PDM peripheral register block.
370  *
371  * @return
372  *   Pending and enabled PDM interrupt sources
373  *   Return value is the bitwise AND of
374  *   - the enabled interrupt sources in PDM_IEN and
375  *   - the pending interrupt flags PDM_IF
376  ******************************************************************************/
PDM_IntGetEnabled(PDM_TypeDef * pdm)377 __STATIC_INLINE uint32_t PDM_IntGetEnabled(PDM_TypeDef *pdm)
378 {
379   uint32_t ien;
380 
381   ien = pdm->IEN;
382   return pdm->IF & ien;
383 }
384 
385 /***************************************************************************//**
386  * @brief
387  *   Set one or more pending PDM interrupts.
388  *
389  * @param[in] pdm
390  *   A pointer to the PDM peripheral register block.
391  *
392  * @param[in] flags
393  *   PDM interrupt sources to set to pending. Use one or more valid
394  *   interrupt flags for the PDM module. The flags are PDM_IFS_DV,
395  *   PDM_IFS_DVL, PDM_IFS_OF and PDM_IFS_UF.
396  ******************************************************************************/
PDM_IntSet(PDM_TypeDef * pdm,uint32_t flags)397 __STATIC_INLINE void PDM_IntSet(PDM_TypeDef *pdm, uint32_t flags)
398 {
399 #if defined(PDM_HAS_SET_CLEAR)
400   pdm->IF_SET = flags;
401 #else
402   pdm->IFS = flags;
403 #endif
404 }
405 
406 /***************************************************************************//**
407  * @brief
408  *   Read one entry from the PDM FIFO.
409  *
410  * @note
411  *   This function will wait until a sample is available in the FIFO.
412  *   Depending on PDM configuration, a FIFO entry can consist of one or two
413  *   samples.
414  *
415  * @param[in] pdm
416  *   A pointer to the PDM peripheral register block.
417  *
418  * @return
419  *   The entry read from the FIFO.
420  ******************************************************************************/
PDM_Rx(PDM_TypeDef * pdm)421 __STATIC_INLINE uint32_t PDM_Rx(PDM_TypeDef *pdm)
422 {
423   while ((pdm->STATUS & PDM_STATUS_EMPTY) == PDM_STATUS_EMPTY) {
424     // Wait for data in FIFO
425   }
426   return pdm->RXDATA;
427 }
428 
429 /***************************************************************************//**
430  * @brief
431  *   Start the PDM operation (start the PDM filter).
432  *
433  * @param[in] pdm
434  *   A pointer to the PDM peripheral register block.
435  ******************************************************************************/
PDM_Start(PDM_TypeDef * pdm)436 __STATIC_INLINE void PDM_Start(PDM_TypeDef *pdm)
437 {
438   while (pdm->SYNCBUSY != 0U) {
439     // Wait for any pending CMD synchronization
440   }
441   pdm->CMD = PDM_CMD_START;
442 }
443 
444 /***************************************************************************//**
445  * @brief
446  *   Get the PDM STATUS register.
447  *
448  * @param[in] pdm
449  *   A pointer to the PDM peripheral register block.
450  *
451  * @return
452  *  STATUS register value.
453  ******************************************************************************/
PDM_StatusGet(PDM_TypeDef * pdm)454 __STATIC_INLINE uint32_t PDM_StatusGet(PDM_TypeDef *pdm)
455 {
456   return pdm->STATUS;
457 }
458 
459 /***************************************************************************//**
460  * @brief
461  *   Stop the PDM operation (stop the PDM filter).
462  *
463  * @param[in] pdm
464  *   A pointer to the PDM peripheral register block.
465  ******************************************************************************/
PDM_Stop(PDM_TypeDef * pdm)466 __STATIC_INLINE void PDM_Stop(PDM_TypeDef *pdm)
467 {
468   while (pdm->SYNCBUSY != 0U) {
469     // Wait for any pending CMD synchronization
470   }
471   pdm->CMD = PDM_CMD_STOP;
472 }
473 
474 /** @} (end addtogroup pdm) */
475 
476 #ifdef __cplusplus
477 }
478 #endif
479 
480 #endif // defined(PDM_PRESENT) && (PDM_COUNT == 1)
481 #endif // EM_PDM_H
482