1 /***************************************************************************//**
2  * @file
3  * @brief Current Digital to Analog Converter (IDAC) 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_idac.h"
32 #if defined(IDAC_COUNT) && (IDAC_COUNT > 0)
33 #include "em_cmu.h"
34 #include "sl_assert.h"
35 #include "em_bus.h"
36 
37 /***************************************************************************//**
38  * @addtogroup idac
39  * @{
40  ******************************************************************************/
41 
42 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
43 /* Fix for errata IDAC_E101 - IDAC output current degradation */
44 #if defined(_SILICON_LABS_32B_SERIES_0) \
45   && (defined(_EFM32_ZERO_FAMILY) || defined(_EFM32_HAPPY_FAMILY))
46 #define ERRATA_FIX_IDAC_E101_EN
47 #endif
48 /** @endcond */
49 
50 /*******************************************************************************
51  **************************   GLOBAL FUNCTIONS   *******************************
52  ******************************************************************************/
53 
54 /***************************************************************************//**
55  * @brief
56  *   Initialize IDAC.
57  *
58  * @details
59  *   Initializes IDAC according to the initialization structure parameter and
60  *   sets the default calibration value stored in the DEVINFO structure.
61  *
62  * @note
63  *   This function will disable IDAC prior to configuration.
64  *
65  * @param[in] idac
66  *   A pointer to the IDAC peripheral register block.
67  *
68  * @param[in] init
69  *   A pointer to the IDAC initialization structure.
70  ******************************************************************************/
IDAC_Init(IDAC_TypeDef * idac,const IDAC_Init_TypeDef * init)71 void IDAC_Init(IDAC_TypeDef *idac, const IDAC_Init_TypeDef *init)
72 {
73   uint32_t tmp;
74 
75   EFM_ASSERT(IDAC_REF_VALID(idac));
76 
77   tmp = (uint32_t)(init->prsSel);
78 
79   tmp |= init->outMode;
80 
81   if (init->enable) {
82     tmp |= IDAC_CTRL_EN;
83   }
84   if (init->prsEnable) {
85 #if defined(_IDAC_CTRL_OUTENPRS_MASK)
86     tmp |= IDAC_CTRL_OUTENPRS;
87 #else
88     tmp |= IDAC_CTRL_APORTOUTENPRS;
89 #endif
90   }
91 #if defined(_IDAC_CTRL_MAINOUTENPRS_MASK)
92   if (init->prsEnableMain) {
93     tmp |= IDAC_CTRL_MAINOUTENPRS;
94   }
95 #endif
96   if (init->sinkEnable) {
97     tmp |= IDAC_CTRL_CURSINK;
98   }
99 
100   idac->CTRL = tmp;
101 }
102 
103 /***************************************************************************//**
104  * @brief
105  *   Enable/disable IDAC.
106  *
107  * @param[in] idac
108  *   A pointer to the IDAC peripheral register block.
109  *
110  * @param[in] enable
111  *   True to enable IDAC, false to disable.
112  ******************************************************************************/
IDAC_Enable(IDAC_TypeDef * idac,bool enable)113 void IDAC_Enable(IDAC_TypeDef *idac, bool enable)
114 {
115   EFM_ASSERT(IDAC_REF_VALID(idac));
116   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_EN_SHIFT, enable);
117 }
118 
119 /***************************************************************************//**
120  * @brief
121  *   Reset IDAC to the same state that it was in after a hardware reset.
122  *
123  * @param[in] idac
124  *   A pointer to the IDAC peripheral register block.
125  ******************************************************************************/
IDAC_Reset(IDAC_TypeDef * idac)126 void IDAC_Reset(IDAC_TypeDef *idac)
127 {
128   EFM_ASSERT(IDAC_REF_VALID(idac));
129 
130 #if defined(ERRATA_FIX_IDAC_E101_EN)
131   /* Fix for errata IDAC_E101 - IDAC output current degradation:
132      Instead of disabling, it will be put in its lowest power state (50 nA)
133      to avoid degradation over time. */
134 
135   /* Make sure IDAC is enabled with a disabled output. */
136   idac->CTRL = _IDAC_CTRL_RESETVALUE | IDAC_CTRL_EN;
137 
138   /* Set the lowest current (50 nA). */
139   idac->CURPROG = IDAC_CURPROG_RANGESEL_RANGE0
140                   | (0x0 << _IDAC_CURPROG_STEPSEL_SHIFT);
141 
142   /* Enable duty-cycling for all energy modes. */
143   idac->DUTYCONFIG = IDAC_DUTYCONFIG_DUTYCYCLEEN;
144 #else
145   idac->CTRL       = _IDAC_CTRL_RESETVALUE;
146   idac->CURPROG    = _IDAC_CURPROG_RESETVALUE;
147   idac->DUTYCONFIG = _IDAC_DUTYCONFIG_RESETVALUE;
148 #endif
149 #if defined (_IDAC_CAL_MASK)
150   idac->CAL        = _IDAC_CAL_RESETVALUE;
151 #endif
152 }
153 
154 /***************************************************************************//**
155  * @brief
156  *   Enable/disable Minimal Output Transition mode.
157  *
158  * @param[in] idac
159  *   A pointer to the IDAC peripheral register block.
160  *
161  * @param[in] enable
162  *   True to enable Minimal Output Transition mode, false to disable.
163  ******************************************************************************/
IDAC_MinimalOutputTransitionMode(IDAC_TypeDef * idac,bool enable)164 void IDAC_MinimalOutputTransitionMode(IDAC_TypeDef *idac, bool enable)
165 {
166   EFM_ASSERT(IDAC_REF_VALID(idac));
167   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_MINOUTTRANS_SHIFT, enable);
168 }
169 
170 /***************************************************************************//**
171  * @brief
172  *   Set the current range of the IDAC output.
173  *
174  * @details
175  *   This function sets the current range of the IDAC output. The function
176  *   also updates the IDAC calibration register (IDAC_CAL) with the default
177  *   calibration value from DEVINFO (factory calibration) corresponding to the
178  *   specified range.
179  *
180  * @param[in] idac
181  *   A pointer to the IDAC peripheral register block.
182  *
183  * @param[in] range
184  *   The current range value.
185  ******************************************************************************/
IDAC_RangeSet(IDAC_TypeDef * idac,const IDAC_Range_TypeDef range)186 void IDAC_RangeSet(IDAC_TypeDef *idac, const IDAC_Range_TypeDef range)
187 {
188   uint32_t tmp;
189 #if defined(_IDAC_CURPROG_TUNING_MASK)
190   uint32_t diCal0;
191   uint32_t diCal1;
192 #endif
193 
194   EFM_ASSERT(IDAC_REF_VALID(idac));
195   EFM_ASSERT(((uint32_t)range >> _IDAC_CURPROG_RANGESEL_SHIFT)
196              <= (_IDAC_CURPROG_RANGESEL_MASK >> _IDAC_CURPROG_RANGESEL_SHIFT));
197 
198 #if defined (_IDAC_CAL_MASK)
199 
200   /* Load proper calibration data depending on the selected range. */
201   switch ((IDAC_Range_TypeDef)range) {
202     case idacCurrentRange0:
203       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE0_MASK)
204                   >> _DEVINFO_IDAC0CAL0_RANGE0_SHIFT;
205       break;
206     case idacCurrentRange1:
207       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE1_MASK)
208                   >> _DEVINFO_IDAC0CAL0_RANGE1_SHIFT;
209       break;
210     case idacCurrentRange2:
211       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE2_MASK)
212                   >> _DEVINFO_IDAC0CAL0_RANGE2_SHIFT;
213       break;
214     case idacCurrentRange3:
215       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE3_MASK)
216                   >> _DEVINFO_IDAC0CAL0_RANGE3_SHIFT;
217       break;
218   }
219 
220   tmp  = idac->CURPROG & ~_IDAC_CURPROG_RANGESEL_MASK;
221   tmp |= (uint32_t)range;
222 
223 #elif defined(_IDAC_CURPROG_TUNING_MASK)
224 
225   /* Load calibration data depending on the selected range and sink/source mode */
226   /* TUNING (calibration) field in CURPROG register. */
227   EFM_ASSERT(idac == IDAC0);
228   diCal0 = DEVINFO->IDAC0CAL0;
229   diCal1 = DEVINFO->IDAC0CAL1;
230 
231   tmp = idac->CURPROG & ~(_IDAC_CURPROG_TUNING_MASK
232                           | _IDAC_CURPROG_RANGESEL_MASK);
233   if (idac->CTRL & IDAC_CTRL_CURSINK) {
234     switch (range) {
235       case idacCurrentRange0:
236         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE0TUNING_MASK)
237                 >> _DEVINFO_IDAC0CAL1_SINKRANGE0TUNING_SHIFT)
238                << _IDAC_CURPROG_TUNING_SHIFT;
239         break;
240 
241       case idacCurrentRange1:
242         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE1TUNING_MASK)
243                 >> _DEVINFO_IDAC0CAL1_SINKRANGE1TUNING_SHIFT)
244                << _IDAC_CURPROG_TUNING_SHIFT;
245         break;
246 
247       case idacCurrentRange2:
248         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE2TUNING_MASK)
249                 >> _DEVINFO_IDAC0CAL1_SINKRANGE2TUNING_SHIFT)
250                << _IDAC_CURPROG_TUNING_SHIFT;
251         break;
252 
253       case idacCurrentRange3:
254         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE3TUNING_MASK)
255                 >> _DEVINFO_IDAC0CAL1_SINKRANGE3TUNING_SHIFT)
256                << _IDAC_CURPROG_TUNING_SHIFT;
257         break;
258     }
259   } else {
260     switch (range) {
261       case idacCurrentRange0:
262         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE0TUNING_MASK)
263                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE0TUNING_SHIFT)
264                << _IDAC_CURPROG_TUNING_SHIFT;
265         break;
266 
267       case idacCurrentRange1:
268         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE1TUNING_MASK)
269                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE1TUNING_SHIFT)
270                << _IDAC_CURPROG_TUNING_SHIFT;
271         break;
272 
273       case idacCurrentRange2:
274         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE2TUNING_MASK)
275                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE2TUNING_SHIFT)
276                << _IDAC_CURPROG_TUNING_SHIFT;
277         break;
278 
279       case idacCurrentRange3:
280         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE3TUNING_MASK)
281                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE3TUNING_SHIFT)
282                << _IDAC_CURPROG_TUNING_SHIFT;
283         break;
284     }
285   }
286 
287   tmp |= (uint32_t)range;
288 
289 #else
290 #warning "IDAC calibration register definition unknown."
291 #endif
292 
293   idac->CURPROG = tmp;
294 }
295 
296 /***************************************************************************//**
297  * @brief
298  *   Set the current step of the IDAC output.
299  *
300  * @param[in] idac
301  *   A pointer to the IDAC peripheral register block.
302  *
303  * @param[in] step
304  *   A step value for the IDAC output. A valid range is 0-31.
305  ******************************************************************************/
IDAC_StepSet(IDAC_TypeDef * idac,const uint32_t step)306 void IDAC_StepSet(IDAC_TypeDef *idac, const uint32_t step)
307 {
308   uint32_t tmp;
309 
310   EFM_ASSERT(IDAC_REF_VALID(idac));
311   EFM_ASSERT(step <= (_IDAC_CURPROG_STEPSEL_MASK >> _IDAC_CURPROG_STEPSEL_SHIFT));
312 
313   tmp  = idac->CURPROG & ~_IDAC_CURPROG_STEPSEL_MASK;
314   tmp |= step << _IDAC_CURPROG_STEPSEL_SHIFT;
315 
316   idac->CURPROG = tmp;
317 }
318 
319 /***************************************************************************//**
320  * @brief
321  *   Enable/disable the IDAC OUT pin.
322  *
323  *  @warning
324  *   This function will not enable/disable the IDAC OUT pin if APORTOUTENPRS is set
325  *   because output enable will be controlled by PRS.
326  *
327  * @param[in] idac
328  *   A pointer to the IDAC peripheral register block.
329  *
330  * @param[in] enable
331  *   True to enable the IDAC OUT pin, false to disable.
332  ******************************************************************************/
IDAC_OutEnable(IDAC_TypeDef * idac,bool enable)333 void IDAC_OutEnable(IDAC_TypeDef *idac, bool enable)
334 {
335   EFM_ASSERT(IDAC_REF_VALID(idac));
336 #if defined(_IDAC_CTRL_OUTEN_MASK)
337   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_OUTEN_SHIFT, enable);
338 #else
339   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_APORTOUTEN_SHIFT, enable);
340 #endif
341 }
342 
343 #if defined(_IDAC_CTRL_MAINOUTEN_MASK)
344 /***************************************************************************//**
345  * @brief
346  *   Enable/disable the IDAC OUTPAD output.
347  *
348  * @note
349  *   IDAC OUTPAD is not available on some EFR32xG1 series devices as well as on
350  *   non-BGA125 EFR32MG12 devices either because of lack of that feature (former)
351  *   or because OUTPAD pin is not available on other packages (latter).
352  *
353  *  @warning
354  *   This function will not enable/disable the IDAC OUTPAD pin if MAINOUTENPRS is set
355  *   because output enable will be controlled by PRS.
356  *
357  * @param[in] idac
358  *   A pointer to the IDAC peripheral register block.
359  *
360  * @param[in] enable
361  *   True to enable the IDAC OUTPAD, false to disable.
362  ******************************************************************************/
IDAC_OutpadEnable(IDAC_TypeDef * idac,bool enable)363 void IDAC_OutpadEnable(IDAC_TypeDef *idac, bool enable)
364 {
365   EFM_ASSERT(IDAC_REF_VALID(idac));
366 
367   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_MAINOUTEN_SHIFT, enable);
368 }
369 #endif
370 
371 /** @} (end addtogroup idac) */
372 
373 #endif /* defined(IDAC_COUNT) && (IDAC_COUNT > 0) */
374