1 /***************************************************************************//**
2  * @file
3  * @brief Analog Comparator (ACMP) 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 #include "em_acmp.h"
32 #if defined(ACMP_COUNT) && (ACMP_COUNT > 0)
33 
34 #include <stdbool.h>
35 #include "em_bus.h"
36 #include "sl_assert.h"
37 #include "em_gpio.h"
38 
39 /***************************************************************************//**
40  * @addtogroup acmp ACMP - Analog Comparator
41  * @brief Analog comparator (ACMP) Peripheral API
42  *
43  * @details
44  *  The Analog Comparator is used to compare voltage of two analog inputs
45  *  with a digital output indicating which input voltage is higher. Inputs can
46  *  either be one of the selectable internal references or from external pins.
47  *  Response time and current consumption can be configured by
48  *  altering the current supply to the comparator.
49  *
50  *  ACMP is available down to EM3 and is able to wake up the system when
51  *  input signals pass a certain threshold. Use @ref ACMP_IntEnable() to enable
52  *  an edge interrupt to use this functionality.
53  *
54  *  This example shows how to use the em_acmp.h API for comparing an input
55  *  pin to an internal 2.5 V reference voltage.
56  *
57  *  @if DOXYDOC_P1_DEVICE
58  *  @include em_acmp_compare_s0.c
59  *  @endif
60  *
61  *  @if DOXYDOC_P2_DEVICE
62  *  @include em_acmp_compare_s1.c
63  *  @endif
64  *
65  *  @if DOXYDOC_S2_DEVICE
66  *  @include em_acmp_compare_s2.c
67  *  @endif
68  *
69  * @note
70  *  ACMP can also be used to compare two separate input pins.
71  *
72  * @details
73  *  ACMP also contains specialized hardware for capacitive sensing. This
74  *  module contains the @ref ACMP_CapsenseInit() function to initialize
75  *  ACMP for capacitive sensing and the @ref ACMP_CapsenseChannelSet() function
76  *  to select the current capsense channel.
77  *
78  *  For applications that require capacitive sensing it is recommended to use a
79  *  library, such as cslib, which is provided by Silicon Labs.
80  *
81  * @{
82  ******************************************************************************/
83 
84 /*******************************************************************************
85  *******************************   DEFINES   ***********************************
86  ******************************************************************************/
87 
88 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
89 
90 /** Validation of ACMP register block pointer reference
91  *  for assert statements. */
92 #if (ACMP_COUNT == 1)
93 #define ACMP_REF_VALID(ref)    ((ref) == ACMP0)
94 #elif (ACMP_COUNT == 2)
95 #define ACMP_REF_VALID(ref)    (((ref) == ACMP0) || ((ref) == ACMP1))
96 #elif (ACMP_COUNT == 3)
97 #define ACMP_REF_VALID(ref)    (((ref) == ACMP0) || ((ref) == ACMP1) || ((ref) == ACMP2))
98 #elif (ACMP_COUNT == 4)
99 #define ACMP_REF_VALID(ref)    (((ref) == ACMP0)    \
100                                 || ((ref) == ACMP1) \
101                                 || ((ref) == ACMP2) \
102                                 || ((ref) == ACMP3))
103 #else
104 #error Undefined number of analog comparators (ACMP).
105 #endif
106 
107 /** The maximum value that can be inserted in the route location register
108  *  for the specific device. */
109 #if defined(_ACMP_ROUTE_LOCATION_LOC3)
110 #define _ACMP_ROUTE_LOCATION_MAX     _ACMP_ROUTE_LOCATION_LOC3
111 #elif defined(_ACMP_ROUTE_LOCATION_LOC2)
112 #define _ACMP_ROUTE_LOCATION_MAX     _ACMP_ROUTE_LOCATION_LOC2
113 #elif defined(_ACMP_ROUTE_LOCATION_LOC1)
114 #define _ACMP_ROUTE_LOCATION_MAX     _ACMP_ROUTE_LOCATION_LOC1
115 #elif defined(_ACMP_ROUTELOC0_OUTLOC_LOC31)
116 #define _ACMP_ROUTE_LOCATION_MAX     _ACMP_ROUTELOC0_OUTLOC_LOC31
117 #elif defined(_ACMP_ROUTELOC0_OUTLOC_MASK)
118 #define _ACMP_ROUTE_LOCATION_MAX     _ACMP_ROUTELOC0_OUTLOC_MASK
119 #endif
120 
121 /** Map ACMP reference to index of device. */
122 #if (ACMP_COUNT == 1)
123 #define ACMP_DEVICE_ID(acmp) ( \
124     (acmp) == ACMP0  ? 0       \
125     : 0)
126 #elif (ACMP_COUNT == 2)
127 #define ACMP_DEVICE_ID(acmp) ( \
128     (acmp) == ACMP0  ? 0       \
129     : (acmp) == ACMP1  ? 1     \
130     : 0)
131 #endif
132 
133 /** @endcond */
134 
135 /*******************************************************************************
136  **************************   GLOBAL FUNCTIONS   *******************************
137  ******************************************************************************/
138 
139 /***************************************************************************//**
140  * @brief
141  *   Set up ACMP for use in capacitive sense applications.
142  *
143  * @details
144  *   This function sets up ACMP for use in capacitive sense applications.
145  *   To use the capacitive sense functionality in the ACMP, use
146  *   the PRS output of the ACMP module to count the number of oscillations
147  *   in the capacitive sense circuit (possibly using a TIMER).
148  *
149  * @note
150  *   A basic example of capacitive sensing can be found in the STK BSP
151  *   (capsense demo).
152  *
153  * @cond DOXYDOC_S2_DEVICE
154  * @note
155  *   A call to ACMP_CapsenseInit will enable and disable the ACMP peripheral,
156  *   which can cause side effects if it was previously set up.
157  * @endcond
158  *
159  * @param[in] acmp
160  *   A pointer to the ACMP peripheral register block.
161  *
162  * @param[in] init
163  *   A pointer to the initialization structure used to configure ACMP for capacitive
164  *   sensing operation.
165  ******************************************************************************/
ACMP_CapsenseInit(ACMP_TypeDef * acmp,const ACMP_CapsenseInit_TypeDef * init)166 void ACMP_CapsenseInit(ACMP_TypeDef *acmp, const ACMP_CapsenseInit_TypeDef *init)
167 {
168   EFM_ASSERT(ACMP_REF_VALID(acmp));
169 
170 #if defined(_SILICON_LABS_32B_SERIES_2)
171   EFM_ASSERT(init->vrefDiv < 64);
172   EFM_ASSERT(init->biasProg
173              <= (_ACMP_CFG_BIAS_MASK >> _ACMP_CFG_BIAS_SHIFT));
174 
175   ACMP_Disable(acmp);
176   acmp->CFG = (init->biasProg << _ACMP_CFG_BIAS_SHIFT)
177               | (init->hysteresisLevel << _ACMP_CFG_HYST_SHIFT);
178   acmp->CTRL = _ACMP_CTRL_RESETVALUE;
179   ACMP_Enable(acmp);
180   acmp->INPUTCTRL = (init->resistor << _ACMP_INPUTCTRL_CSRESSEL_SHIFT)
181                     | (init->vrefDiv << _ACMP_INPUTCTRL_VREFDIV_SHIFT)
182                     | (ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
183   if (!init->enable) {
184     ACMP_Disable(acmp);
185   }
186 
187 #elif defined(_SILICON_LABS_32B_SERIES_1)
188   EFM_ASSERT(init->vddLevelLow < 64);
189   EFM_ASSERT(init->vddLevelHigh < 64);
190   EFM_ASSERT(init->biasProg
191              <= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
192 
193   /* Set the control register. No need to set interrupt modes. */
194   acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
195                | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
196                | ACMP_CTRL_ACCURACY_HIGH;
197   acmp->HYSTERESIS0 = (init->vddLevelHigh      << _ACMP_HYSTERESIS0_DIVVA_SHIFT)
198                       | (init->hysteresisLevel_0 << _ACMP_HYSTERESIS0_HYST_SHIFT);
199   acmp->HYSTERESIS1 = (init->vddLevelLow       << _ACMP_HYSTERESIS1_DIVVA_SHIFT)
200                       | (init->hysteresisLevel_1 << _ACMP_HYSTERESIS1_HYST_SHIFT);
201   /* Select capacitive sensing mode by selecting a resistor and enabling it. */
202   acmp->INPUTSEL = (init->resistor << _ACMP_INPUTSEL_CSRESSEL_SHIFT)
203                    | ACMP_INPUTSEL_CSRESEN
204                    | ACMP_INPUTSEL_VASEL_VDD
205                    | ACMP_INPUTSEL_NEGSEL_VADIV;
206   BUS_RegBitWrite(&acmp->CTRL, _ACMP_CTRL_EN_SHIFT, init->enable);
207 
208 #elif defined(_SILICON_LABS_32B_SERIES_0)
209   EFM_ASSERT(init->vddLevel < 64);
210   EFM_ASSERT(init->biasProg
211              <= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
212 
213   /* Set the control register. No need to set interrupt modes. */
214   acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
215                | (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT)
216                | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
217                | (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT)
218                | (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT);
219   /* Select capacitive sensing mode by selecting a resistor and enabling it. */
220   acmp->INPUTSEL = (init->resistor << _ACMP_INPUTSEL_CSRESSEL_SHIFT)
221                    | ACMP_INPUTSEL_CSRESEN
222                    | (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT)
223                    | (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT)
224                    | ACMP_INPUTSEL_NEGSEL_CAPSENSE;
225   BUS_RegBitWrite(&acmp->CTRL, _ACMP_CTRL_EN_SHIFT, init->enable);
226 #endif
227 }
228 
229 /***************************************************************************//**
230  * @brief
231  *   Set the ACMP channel used for capacitive sensing.
232  *
233  * @note
234  *   A basic example of capacitive sensing can be found in the STK BSP
235  *   (capsense demo).
236  *
237  * @cond DOXYDOC_S2_DEVICE
238  * @note
239  *   Can only be called when the peripheral is enabled.
240  * @endcond
241  *
242  * @param[in] acmp
243  *   A pointer to the ACMP peripheral register block.
244  *
245  * @param[in] channel
246  *   The ACMP channel to use for capacitive sensing (Possel).
247  ******************************************************************************/
ACMP_CapsenseChannelSet(ACMP_TypeDef * acmp,ACMP_Channel_TypeDef channel)248 void ACMP_CapsenseChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef channel)
249 {
250   /* Make sure the module exists on the selected chip. */
251   EFM_ASSERT(ACMP_REF_VALID(acmp));
252 
253 #if defined(_ACMP_INPUTSEL_POSSEL_CH7)
254   /* Make sure that only external channels are used. */
255   EFM_ASSERT(channel <= _ACMP_INPUTSEL_POSSEL_CH7);
256 #elif defined(_ACMP_INPUTCTRL_POSSEL_PD15)
257   EFM_ASSERT(channel != _ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
258   EFM_ASSERT(_ACMP_INPUTCTRL_POSSEL_PA0 <= channel);
259   EFM_ASSERT(channel <= _ACMP_INPUTCTRL_POSSEL_PD15);
260 #endif
261 
262 #if defined(_ACMP_INPUTCTRL_MASK)
263   /* Make sure that the ACMP is enabled before changing INPUTCTRL. */
264   EFM_ASSERT(acmp->EN & ACMP_EN_EN);
265 
266   while (acmp->SYNCBUSY != 0U) {
267     /* Wait for synchronization to finish */
268   }
269   /* Set channel as positive channel in ACMP */
270   BUS_RegMaskedWrite(&acmp->INPUTCTRL, _ACMP_INPUTCTRL_POSSEL_MASK,
271                      channel << _ACMP_INPUTCTRL_POSSEL_SHIFT);
272 #else
273   /* Set channel as a positive channel in ACMP. */
274   BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_POSSEL_MASK,
275                      channel << _ACMP_INPUTSEL_POSSEL_SHIFT);
276 #endif
277 }
278 
279 /***************************************************************************//**
280  * @brief
281  *   Disable ACMP.
282  *
283  * @param[in] acmp
284  *   A pointer to the ACMP peripheral register block.
285  ******************************************************************************/
ACMP_Disable(ACMP_TypeDef * acmp)286 void ACMP_Disable(ACMP_TypeDef *acmp)
287 {
288   /* Make sure the module exists on the selected chip. */
289   EFM_ASSERT(ACMP_REF_VALID(acmp));
290 
291 #if defined(_ACMP_EN_MASK)
292   while ((acmp->EN != 0U) && (acmp->SYNCBUSY != 0U)) {
293     /* Wait for synchronization to finish */
294   }
295   acmp->EN_CLR = ACMP_EN_EN;
296 
297 #if defined(_ACMP_EN_DISABLING_MASK)
298   while (acmp->EN & _ACMP_EN_DISABLING_MASK) {
299     // Wait for disabling to finish
300   }
301 #endif
302 
303 #else
304   acmp->CTRL &= ~ACMP_CTRL_EN;
305 #endif
306 }
307 
308 /***************************************************************************//**
309  * @brief
310  *   Enable ACMP.
311  *
312  * @param[in] acmp
313  *   A pointer to the ACMP peripheral register block.
314  ******************************************************************************/
ACMP_Enable(ACMP_TypeDef * acmp)315 void ACMP_Enable(ACMP_TypeDef *acmp)
316 {
317   /* Make sure the module exists on the selected chip. */
318   EFM_ASSERT(ACMP_REF_VALID(acmp));
319 
320 #if defined(_ACMP_EN_MASK)
321   acmp->EN_SET = ACMP_EN_EN;
322 #else
323   acmp->CTRL |= ACMP_CTRL_EN;
324 #endif
325 }
326 
327 #if defined(_ACMP_EXTIFCTRL_MASK)
328 /***************************************************************************//**
329  * @brief
330  *   Select and enable external input.
331  *
332  * @details
333  *   This is used when an external module needs to take control of the ACMP
334  *   POSSEL field to configure the APORT input for the ACMP. Modules,
335  *   such as LESENSE, use this to change the ACMP input during a scan sequence.
336  *
337  * @param[in] acmp
338  *   A pointer to the ACMP peripheral register block.
339  *
340  * @param[in] aport
341  *   This parameter decides which APORT(s) the ACMP will use when it's
342  *   controlled by an external module.
343  ******************************************************************************/
ACMP_ExternalInputSelect(ACMP_TypeDef * acmp,ACMP_ExternalInput_Typedef aport)344 void ACMP_ExternalInputSelect(ACMP_TypeDef *acmp, ACMP_ExternalInput_Typedef aport)
345 {
346   acmp->EXTIFCTRL = (aport << _ACMP_EXTIFCTRL_APORTSEL_SHIFT)
347                     | ACMP_EXTIFCTRL_EN;
348   while (!(acmp->STATUS & ACMP_STATUS_EXTIFACT)) {
349   }
350 }
351 #endif
352 
353 /***************************************************************************//**
354  * @brief
355  *   Reset ACMP to the same state that it was in after a hardware reset.
356  *
357  * @note
358  *   The GPIO ACMP ROUTE register is NOT reset by this function to allow for
359  *   centralized setup of this feature.
360  *
361  * @note
362  *   The peripheral may be enabled and disabled during reset.
363  *
364  * @param[in] acmp
365  *   A pointer to the ACMP peripheral register block.
366  ******************************************************************************/
ACMP_Reset(ACMP_TypeDef * acmp)367 void ACMP_Reset(ACMP_TypeDef *acmp)
368 {
369   /* Make sure the module exists on the selected chip */
370   EFM_ASSERT(ACMP_REF_VALID(acmp));
371 
372 #if defined(_SILICON_LABS_32B_SERIES_2)
373 #if defined(ACMP_SWRST_SWRST)
374   acmp->SWRST_SET = ACMP_SWRST_SWRST;
375   while (acmp->SWRST & _ACMP_SWRST_RESETTING_MASK) {
376   }
377 #else
378   acmp->IEN         = _ACMP_IEN_RESETVALUE;
379   ACMP_Enable(acmp);
380   acmp->INPUTCTRL   = _ACMP_INPUTCTRL_RESETVALUE;
381   ACMP_Disable(acmp);
382   acmp->CFG         = PM5507_ACMP_CFG_RESETVALUE;
383   acmp->CTRL        = _ACMP_CTRL_RESETVALUE;
384   acmp->IF_CLR      = _ACMP_IF_MASK;
385 #endif
386 #else // Series 0 and Series 1 devices
387   acmp->IEN         = _ACMP_IEN_RESETVALUE;
388   acmp->CTRL        = _ACMP_CTRL_RESETVALUE;
389   acmp->INPUTSEL    = _ACMP_INPUTSEL_RESETVALUE;
390 #if defined(_ACMP_HYSTERESIS0_HYST_MASK)
391   acmp->HYSTERESIS0 = _ACMP_HYSTERESIS0_RESETVALUE;
392   acmp->HYSTERESIS1 = _ACMP_HYSTERESIS1_RESETVALUE;
393 #endif
394   acmp->IFC         = _ACMP_IF_MASK;
395 #endif
396 }
397 
398 #if defined(_GPIO_ACMP_ROUTEEN_MASK)
399 /***************************************************************************//**
400  * @brief
401  *   Sets up GPIO output from the ACMP.
402  *
403  * @note
404  *    GPIO must be enabled in the CMU before this function call, i.e.
405  * @verbatim CMU_ClockEnable(cmuClock_GPIO, true); @endverbatim
406  *
407  * @param[in] acmp
408  *   Pointer to the ACMP peripheral register block.
409  *
410  * @param port
411  *   The GPIO port to use.
412  *
413  * @param pin
414  *   The GPIO pin to use.
415  *
416  * @param enable
417  *   Enable or disable pin output.
418  *
419  * @param invert
420  *   Invert output.
421  ******************************************************************************/
ACMP_GPIOSetup(ACMP_TypeDef * acmp,GPIO_Port_TypeDef port,unsigned int pin,bool enable,bool invert)422 void ACMP_GPIOSetup(ACMP_TypeDef *acmp, GPIO_Port_TypeDef port,
423                     unsigned int pin, bool enable, bool invert)
424 {
425   int acmpIndex = ACMP_DEVICE_ID(acmp);
426 
427   /* Make sure the module exists on the selected chip */
428   EFM_ASSERT(ACMP_REF_VALID(acmp));
429 
430   /* Make sure that the port/pin combination is valid. */
431   EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
432 
433   /* Set GPIO inversion */
434   acmp->CTRL = (acmp->CTRL & _ACMP_CTRL_NOTRDYVAL_MASK)
435                | (invert << _ACMP_CTRL_GPIOINV_SHIFT);
436 
437   GPIO->ACMPROUTE[acmpIndex].ACMPOUTROUTE = (port << _GPIO_ACMP_ACMPOUTROUTE_PORT_SHIFT)
438                                             | (pin << _GPIO_ACMP_ACMPOUTROUTE_PIN_SHIFT);
439   GPIO->ACMPROUTE[acmpIndex].ROUTEEN  = enable ? GPIO_ACMP_ROUTEEN_ACMPOUTPEN : 0;
440 }
441 #else
442 /***************************************************************************//**
443  * @brief
444  *   Set up GPIO output from ACMP.
445  *
446  * @note
447  *    GPIO must be enabled in the CMU before this function call, i.e.,
448  * @verbatim CMU_ClockEnable(cmuClock_GPIO, true); @endverbatim
449  *
450  * @param[in] acmp
451  *   A pointer to the ACMP peripheral register block.
452  *
453  * @param location
454  *   The pin location to use. See the data sheet for location to pin mappings.
455  *
456  * @param enable
457  *   Enable or disable pin output.
458  *
459  * @param invert
460  *   Invert output.
461  ******************************************************************************/
ACMP_GPIOSetup(ACMP_TypeDef * acmp,uint32_t location,bool enable,bool invert)462 void ACMP_GPIOSetup(ACMP_TypeDef *acmp, uint32_t location, bool enable, bool invert)
463 {
464   /* Make sure the module exists on the selected chip */
465   EFM_ASSERT(ACMP_REF_VALID(acmp));
466 
467   /* Sanity checking of location */
468   EFM_ASSERT(location <= _ACMP_ROUTE_LOCATION_MAX);
469 
470   /* Set GPIO inversion */
471   BUS_RegMaskedWrite(&acmp->CTRL, _ACMP_CTRL_GPIOINV_MASK,
472                      invert << _ACMP_CTRL_GPIOINV_SHIFT);
473 
474 #if defined(_ACMP_ROUTE_MASK)
475   acmp->ROUTE = (location << _ACMP_ROUTE_LOCATION_SHIFT)
476                 | (enable << _ACMP_ROUTE_ACMPPEN_SHIFT);
477 #endif
478 #if defined(_ACMP_ROUTELOC0_MASK)
479   acmp->ROUTELOC0 = location << _ACMP_ROUTELOC0_OUTLOC_SHIFT;
480   acmp->ROUTEPEN  = enable ? ACMP_ROUTEPEN_OUTPEN : 0;
481 #endif
482 }
483 #endif /* defined(_GPIO_ACMP_ROUTEEN_MASK) */
484 
485 /***************************************************************************//**
486  * @brief
487  *   Set which channels should be used in ACMP comparisons.
488  *
489  * @cond DOXYDOC_S2_DEVICE
490  * @note
491  *   Can only be called when the peripheral is enabled.
492  *
493  * @note
494  *   If GPIO is used for both posSel and negSel, they cannot both use even
495  *   or odd pins.
496  * @endcond
497  *
498  * @param[in] acmp
499  *   A pointer to the ACMP peripheral register block.
500  *
501  * @param negSel
502  *   A channel to use on the negative input to the ACMP.
503  *
504  * @param posSel
505  *   A channel to use on the positive input to the ACMP.
506  ******************************************************************************/
ACMP_ChannelSet(ACMP_TypeDef * acmp,ACMP_Channel_TypeDef negSel,ACMP_Channel_TypeDef posSel)507 void ACMP_ChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef negSel,
508                      ACMP_Channel_TypeDef posSel)
509 {
510   /* Make sure the module exists on the selected chip. */
511   EFM_ASSERT(ACMP_REF_VALID(acmp));
512 
513   /* Make sure that posSel and negSel channel selectors are valid. */
514 #if defined(_ACMP_INPUTSEL_NEGSEL_DAC0CH1)
515   EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_DAC0CH1);
516 #elif defined(_ACMP_INPUTSEL_NEGSEL_CAPSENSE)
517   EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_CAPSENSE);
518 #endif
519 
520 #if defined(_ACMP_INPUTSEL_POSSEL_CH7)
521   EFM_ASSERT(posSel <= _ACMP_INPUTSEL_POSSEL_CH7);
522 #endif
523 
524   /* Make sure that posSel and negSel channel selectors are valid. */
525 #if defined(_ACMP_INPUTCTRL_POSSEL_PD15)
526   EFM_ASSERT(negSel <= _ACMP_INPUTCTRL_POSSEL_PD15);
527   EFM_ASSERT(posSel <= _ACMP_INPUTCTRL_POSSEL_PD15);
528   EFM_ASSERT(posSel != _ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
529 
530   /* Make sure that posSel and negSel channel selectors don't both
531    * use odd or even pins. */
532 #if (_SILICON_LABS_32B_SERIES_2_CONFIG > 2)
533   EFM_ASSERT(!((((posSel >= _ACMP_INPUTCTRL_POSSEL_EXTPA)
534                  && (posSel <= _ACMP_INPUTCTRL_POSSEL_EXTPD))
535                 || (posSel >= _ACMP_INPUTCTRL_POSSEL_PA0))
536                && (negSel >= _ACMP_INPUTCTRL_NEGSEL_PA0)
537                && (posSel % 2 == negSel % 2)));
538 #else
539   EFM_ASSERT(!((posSel >= _ACMP_INPUTCTRL_POSSEL_PA0)
540                && (negSel >= _ACMP_INPUTCTRL_NEGSEL_PA0)
541                && (posSel % 2 == negSel % 2)));
542 
543 #endif
544 #endif
545 
546 #if defined(_ACMP_INPUTCTRL_MASK)
547   /* Make sure that the ACMP is enabled before changing INPUTCTRL. */
548   EFM_ASSERT(acmp->EN & ACMP_EN_EN);
549   while (acmp->SYNCBUSY != 0U) {
550     /* Wait for synchronization to finish */
551   }
552   acmp->INPUTCTRL = (acmp->INPUTCTRL & ~(_ACMP_INPUTCTRL_POSSEL_MASK
553                                          | _ACMP_INPUTCTRL_NEGSEL_MASK))
554                     | (negSel << _ACMP_INPUTCTRL_NEGSEL_SHIFT)
555                     | (posSel << _ACMP_INPUTCTRL_POSSEL_SHIFT);
556 #else
557   acmp->INPUTSEL = (acmp->INPUTSEL & ~(_ACMP_INPUTSEL_POSSEL_MASK
558                                        | _ACMP_INPUTSEL_NEGSEL_MASK))
559                    | (negSel << _ACMP_INPUTSEL_NEGSEL_SHIFT)
560                    | (posSel << _ACMP_INPUTSEL_POSSEL_SHIFT);
561 #endif
562 }
563 
564 /***************************************************************************//**
565  * @brief
566  *   Initialize ACMP.
567  *
568  * @cond DOXYDOC_S2_DEVICE
569  * @note
570  *   A call to ACMP_Init can cause side effects since it can enable/disable
571  *   the peripheral.
572  * @endcond
573  *
574  * @param[in] acmp
575  *   A pointer to the ACMP peripheral register block.
576  *
577  * @param[in] init
578  *   A pointer to the initialization structure used to configure ACMP.
579  ******************************************************************************/
ACMP_Init(ACMP_TypeDef * acmp,const ACMP_Init_TypeDef * init)580 void ACMP_Init(ACMP_TypeDef *acmp, const ACMP_Init_TypeDef *init)
581 {
582   /* Make sure the module exists on the selected chip. */
583   EFM_ASSERT(ACMP_REF_VALID(acmp));
584 
585 #if defined(_SILICON_LABS_32B_SERIES_2)
586   EFM_ASSERT(init->biasProg
587              <= (_ACMP_CFG_BIAS_MASK >> _ACMP_CFG_BIAS_SHIFT));
588 
589 // PM-5507: enforce that biasProg is a functional value
590 #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
591   EFM_ASSERT(init->biasProg >= 4);
592 #elif defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3)
593   // Allow customer to use BIASPROG in [2; 3]
594   EFM_ASSERT(init->biasProg >= 2);
595 #else
596   // Allow customer to use BIASPROG in [0; 3]
597   // but the implementation of the wait operation would be their responsibility
598 #endif
599 
600   /* Make sure the ACMP is disabled since ACMP power source might be changed.*/
601   ACMP_Disable(acmp);
602 
603   acmp->CFG = (init->biasProg << _ACMP_CFG_BIAS_SHIFT)
604               | (init->inputRange << _ACMP_CFG_INPUTRANGE_SHIFT)
605               | (init->accuracy << _ACMP_CFG_ACCURACY_SHIFT)
606               | (init->hysteresisLevel << _ACMP_CFG_HYST_SHIFT);
607   acmp->CTRL = init->inactiveValue << _ACMP_CTRL_NOTRDYVAL_SHIFT;
608   ACMP_Enable(acmp);
609   BUS_RegMaskedWrite(&acmp->INPUTCTRL, _ACMP_INPUTCTRL_VREFDIV_MASK,
610                      init->vrefDiv << _ACMP_INPUTCTRL_VREFDIV_SHIFT);
611 
612 #elif defined(_SILICON_LABS_32B_SERIES_1)
613   EFM_ASSERT(init->biasProg
614              <= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
615   /* Make sure the ACMP is disabled since ACMP power source might be changed.*/
616   ACMP_Disable(acmp);
617 
618   acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
619                | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
620                | (init->interruptOnFallingEdge << _ACMP_CTRL_IFALL_SHIFT)
621                | (init->interruptOnRisingEdge << _ACMP_CTRL_IRISE_SHIFT)
622                | (init->inputRange << _ACMP_CTRL_INPUTRANGE_SHIFT)
623                | (init->accuracy << _ACMP_CTRL_ACCURACY_SHIFT)
624                | (init->powerSource << _ACMP_CTRL_PWRSEL_SHIFT)
625                | (init->inactiveValue << _ACMP_CTRL_INACTVAL_SHIFT);
626   acmp->INPUTSEL = init->vlpInput << _ACMP_INPUTSEL_VLPSEL_SHIFT;
627   acmp->HYSTERESIS0 = init->hysteresisLevel_0;
628   acmp->HYSTERESIS1 = init->hysteresisLevel_1;
629 
630 #elif defined(_SILICON_LABS_32B_SERIES_0)
631   EFM_ASSERT(init->biasProg
632              <= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
633   /* Make sure the ACMP is disabled since ACMP power source might be changed.*/
634   ACMP_Disable(acmp);
635 
636   acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
637                | (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT)
638                | (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
639                | (init->interruptOnFallingEdge << _ACMP_CTRL_IFALL_SHIFT)
640                | (init->interruptOnRisingEdge << _ACMP_CTRL_IRISE_SHIFT)
641                | (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT)
642                | (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT)
643                | (init->inactiveValue << _ACMP_CTRL_INACTVAL_SHIFT);
644   acmp->INPUTSEL = (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT)
645                    | (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT);
646 
647 #endif
648 
649   if (init->enable) {
650     ACMP_Enable(acmp);
651   } else {
652     ACMP_Disable(acmp);
653   }
654 }
655 
656 #if defined(_ACMP_INPUTSEL_VASEL_MASK)
657 /***************************************************************************//**
658  * @brief
659  *   Set up the VA source.
660  *
661  * @param[in] acmp
662  *   A pointer to the ACMP peripheral register block.
663  *
664  * @param[in] vaconfig
665  *   A pointer to the structure used to configure the VA source. This structure
666  *   contains the input source and the 2 divider values.
667  ******************************************************************************/
ACMP_VASetup(ACMP_TypeDef * acmp,const ACMP_VAConfig_TypeDef * vaconfig)668 void ACMP_VASetup(ACMP_TypeDef *acmp, const ACMP_VAConfig_TypeDef *vaconfig)
669 {
670   EFM_ASSERT(vaconfig->div0 < 64);
671   EFM_ASSERT(vaconfig->div1 < 64);
672 
673   BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VASEL_MASK,
674                      vaconfig->input << _ACMP_INPUTSEL_VASEL_SHIFT);
675   BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVA_MASK,
676                      vaconfig->div0 << _ACMP_HYSTERESIS0_DIVVA_SHIFT);
677   BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVA_MASK,
678                      vaconfig->div1 << _ACMP_HYSTERESIS1_DIVVA_SHIFT);
679 }
680 #endif
681 
682 #if defined(_ACMP_INPUTSEL_VBSEL_MASK)
683 /***************************************************************************//**
684  * @brief
685  *   Set up the VB Source.
686  *
687  * @param[in] acmp
688  *   A pointer to the ACMP peripheral register block.
689  *
690  * @param[in] vbconfig
691  *   A pointer to the structure used to configure the VB source. This structure
692  *   contains the input source and the 2 divider values.
693  ******************************************************************************/
ACMP_VBSetup(ACMP_TypeDef * acmp,const ACMP_VBConfig_TypeDef * vbconfig)694 void ACMP_VBSetup(ACMP_TypeDef *acmp, const ACMP_VBConfig_TypeDef *vbconfig)
695 {
696   EFM_ASSERT(vbconfig->div0 < 64);
697   EFM_ASSERT(vbconfig->div1 < 64);
698 
699   BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VBSEL_MASK,
700                      vbconfig->input << _ACMP_INPUTSEL_VBSEL_SHIFT);
701   BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVB_MASK,
702                      vbconfig->div0 << _ACMP_HYSTERESIS0_DIVVB_SHIFT);
703   BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVB_MASK,
704                      vbconfig->div1 << _ACMP_HYSTERESIS1_DIVVB_SHIFT);
705 }
706 #endif
707 
708 /** @} (end addtogroup acmp) */
709 #endif /* defined(ACMP_COUNT) && (ACMP_COUNT > 0) */
710