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 "em_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 (init->sinkEnable) {
92     tmp |= IDAC_CTRL_CURSINK;
93   }
94 
95   idac->CTRL = tmp;
96 }
97 
98 /***************************************************************************//**
99  * @brief
100  *   Enable/disable IDAC.
101  *
102  * @param[in] idac
103  *   A pointer to the IDAC peripheral register block.
104  *
105  * @param[in] enable
106  *   True to enable IDAC, false to disable.
107  ******************************************************************************/
IDAC_Enable(IDAC_TypeDef * idac,bool enable)108 void IDAC_Enable(IDAC_TypeDef *idac, bool enable)
109 {
110   EFM_ASSERT(IDAC_REF_VALID(idac));
111   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_EN_SHIFT, enable);
112 }
113 
114 /***************************************************************************//**
115  * @brief
116  *   Reset IDAC to the same state that it was in after a hardware reset.
117  *
118  * @param[in] idac
119  *   A pointer to the IDAC peripheral register block.
120  ******************************************************************************/
IDAC_Reset(IDAC_TypeDef * idac)121 void IDAC_Reset(IDAC_TypeDef *idac)
122 {
123   EFM_ASSERT(IDAC_REF_VALID(idac));
124 
125 #if defined(ERRATA_FIX_IDAC_E101_EN)
126   /* Fix for errata IDAC_E101 - IDAC output current degradation:
127      Instead of disabling, it will be put in its lowest power state (50 nA)
128      to avoid degradation over time. */
129 
130   /* Make sure IDAC is enabled with a disabled output. */
131   idac->CTRL = _IDAC_CTRL_RESETVALUE | IDAC_CTRL_EN;
132 
133   /* Set the lowest current (50 nA). */
134   idac->CURPROG = IDAC_CURPROG_RANGESEL_RANGE0
135                   | (0x0 << _IDAC_CURPROG_STEPSEL_SHIFT);
136 
137   /* Enable duty-cycling for all energy modes. */
138   idac->DUTYCONFIG = IDAC_DUTYCONFIG_DUTYCYCLEEN;
139 #else
140   idac->CTRL       = _IDAC_CTRL_RESETVALUE;
141   idac->CURPROG    = _IDAC_CURPROG_RESETVALUE;
142   idac->DUTYCONFIG = _IDAC_DUTYCONFIG_RESETVALUE;
143 #endif
144 #if defined (_IDAC_CAL_MASK)
145   idac->CAL        = _IDAC_CAL_RESETVALUE;
146 #endif
147 }
148 
149 /***************************************************************************//**
150  * @brief
151  *   Enable/disable Minimal Output Transition mode.
152  *
153  * @param[in] idac
154  *   A pointer to the IDAC peripheral register block.
155  *
156  * @param[in] enable
157  *   True to enable Minimal Output Transition mode, false to disable.
158  ******************************************************************************/
IDAC_MinimalOutputTransitionMode(IDAC_TypeDef * idac,bool enable)159 void IDAC_MinimalOutputTransitionMode(IDAC_TypeDef *idac, bool enable)
160 {
161   EFM_ASSERT(IDAC_REF_VALID(idac));
162   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_MINOUTTRANS_SHIFT, enable);
163 }
164 
165 /***************************************************************************//**
166  * @brief
167  *   Set the current range of the IDAC output.
168  *
169  * @details
170  *   This function sets the current range of the IDAC output. The function
171  *   also updates the IDAC calibration register (IDAC_CAL) with the default
172  *   calibration value from DEVINFO (factory calibration) corresponding to the
173  *   specified range.
174  *
175  * @param[in] idac
176  *   A pointer to the IDAC peripheral register block.
177  *
178  * @param[in] range
179  *   The current range value.
180  ******************************************************************************/
IDAC_RangeSet(IDAC_TypeDef * idac,const IDAC_Range_TypeDef range)181 void IDAC_RangeSet(IDAC_TypeDef *idac, const IDAC_Range_TypeDef range)
182 {
183   uint32_t tmp;
184 #if defined(_IDAC_CURPROG_TUNING_MASK)
185   uint32_t diCal0;
186   uint32_t diCal1;
187 #endif
188 
189   EFM_ASSERT(IDAC_REF_VALID(idac));
190   EFM_ASSERT(((uint32_t)range >> _IDAC_CURPROG_RANGESEL_SHIFT)
191              <= (_IDAC_CURPROG_RANGESEL_MASK >> _IDAC_CURPROG_RANGESEL_SHIFT));
192 
193 #if defined (_IDAC_CAL_MASK)
194 
195   /* Load proper calibration data depending on the selected range. */
196   switch ((IDAC_Range_TypeDef)range) {
197     case idacCurrentRange0:
198       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE0_MASK)
199                   >> _DEVINFO_IDAC0CAL0_RANGE0_SHIFT;
200       break;
201     case idacCurrentRange1:
202       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE1_MASK)
203                   >> _DEVINFO_IDAC0CAL0_RANGE1_SHIFT;
204       break;
205     case idacCurrentRange2:
206       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE2_MASK)
207                   >> _DEVINFO_IDAC0CAL0_RANGE2_SHIFT;
208       break;
209     case idacCurrentRange3:
210       idac->CAL = (DEVINFO->IDAC0CAL0 & _DEVINFO_IDAC0CAL0_RANGE3_MASK)
211                   >> _DEVINFO_IDAC0CAL0_RANGE3_SHIFT;
212       break;
213   }
214 
215   tmp  = idac->CURPROG & ~_IDAC_CURPROG_RANGESEL_MASK;
216   tmp |= (uint32_t)range;
217 
218 #elif defined(_IDAC_CURPROG_TUNING_MASK)
219 
220   /* Load calibration data depending on the selected range and sink/source mode */
221   /* TUNING (calibration) field in CURPROG register. */
222   EFM_ASSERT(idac == IDAC0);
223   diCal0 = DEVINFO->IDAC0CAL0;
224   diCal1 = DEVINFO->IDAC0CAL1;
225 
226   tmp = idac->CURPROG & ~(_IDAC_CURPROG_TUNING_MASK
227                           | _IDAC_CURPROG_RANGESEL_MASK);
228   if (idac->CTRL & IDAC_CTRL_CURSINK) {
229     switch (range) {
230       case idacCurrentRange0:
231         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE0TUNING_MASK)
232                 >> _DEVINFO_IDAC0CAL1_SINKRANGE0TUNING_SHIFT)
233                << _IDAC_CURPROG_TUNING_SHIFT;
234         break;
235 
236       case idacCurrentRange1:
237         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE1TUNING_MASK)
238                 >> _DEVINFO_IDAC0CAL1_SINKRANGE1TUNING_SHIFT)
239                << _IDAC_CURPROG_TUNING_SHIFT;
240         break;
241 
242       case idacCurrentRange2:
243         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE2TUNING_MASK)
244                 >> _DEVINFO_IDAC0CAL1_SINKRANGE2TUNING_SHIFT)
245                << _IDAC_CURPROG_TUNING_SHIFT;
246         break;
247 
248       case idacCurrentRange3:
249         tmp |= ((diCal1 & _DEVINFO_IDAC0CAL1_SINKRANGE3TUNING_MASK)
250                 >> _DEVINFO_IDAC0CAL1_SINKRANGE3TUNING_SHIFT)
251                << _IDAC_CURPROG_TUNING_SHIFT;
252         break;
253     }
254   } else {
255     switch (range) {
256       case idacCurrentRange0:
257         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE0TUNING_MASK)
258                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE0TUNING_SHIFT)
259                << _IDAC_CURPROG_TUNING_SHIFT;
260         break;
261 
262       case idacCurrentRange1:
263         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE1TUNING_MASK)
264                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE1TUNING_SHIFT)
265                << _IDAC_CURPROG_TUNING_SHIFT;
266         break;
267 
268       case idacCurrentRange2:
269         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE2TUNING_MASK)
270                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE2TUNING_SHIFT)
271                << _IDAC_CURPROG_TUNING_SHIFT;
272         break;
273 
274       case idacCurrentRange3:
275         tmp |= ((diCal0 & _DEVINFO_IDAC0CAL0_SOURCERANGE3TUNING_MASK)
276                 >> _DEVINFO_IDAC0CAL0_SOURCERANGE3TUNING_SHIFT)
277                << _IDAC_CURPROG_TUNING_SHIFT;
278         break;
279     }
280   }
281 
282   tmp |= (uint32_t)range;
283 
284 #else
285 #warning "IDAC calibration register definition unknown."
286 #endif
287 
288   idac->CURPROG = tmp;
289 }
290 
291 /***************************************************************************//**
292  * @brief
293  *   Set the current step of the IDAC output.
294  *
295  * @param[in] idac
296  *   A pointer to the IDAC peripheral register block.
297  *
298  * @param[in] step
299  *   A step value for the IDAC output. A valid range is 0-31.
300  ******************************************************************************/
IDAC_StepSet(IDAC_TypeDef * idac,const uint32_t step)301 void IDAC_StepSet(IDAC_TypeDef *idac, const uint32_t step)
302 {
303   uint32_t tmp;
304 
305   EFM_ASSERT(IDAC_REF_VALID(idac));
306   EFM_ASSERT(step <= (_IDAC_CURPROG_STEPSEL_MASK >> _IDAC_CURPROG_STEPSEL_SHIFT));
307 
308   tmp  = idac->CURPROG & ~_IDAC_CURPROG_STEPSEL_MASK;
309   tmp |= step << _IDAC_CURPROG_STEPSEL_SHIFT;
310 
311   idac->CURPROG = tmp;
312 }
313 
314 /***************************************************************************//**
315  * @brief
316  *   Enable/disable the IDAC OUT pin.
317  *
318  * @param[in] idac
319  *   A pointer to the IDAC peripheral register block.
320  *
321  * @param[in] enable
322  *   True to enable the IDAC OUT pin, false to disable.
323  ******************************************************************************/
IDAC_OutEnable(IDAC_TypeDef * idac,bool enable)324 void IDAC_OutEnable(IDAC_TypeDef *idac, bool enable)
325 {
326   EFM_ASSERT(IDAC_REF_VALID(idac));
327 #if defined(_IDAC_CTRL_OUTEN_MASK)
328   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_OUTEN_SHIFT, enable);
329 #else
330   BUS_RegBitWrite(&idac->CTRL, _IDAC_CTRL_APORTOUTEN_SHIFT, enable);
331 #endif
332 }
333 
334 /** @} (end addtogroup idac) */
335 
336 #endif /* defined(IDAC_COUNT) && (IDAC_COUNT > 0) */
337