1 /***************************************************************************//**
2  * @file
3  * @brief DCDC Coulomb Counter (DCDC_COULOMB_COUNTER) peripheral API
4  *******************************************************************************
5  * # License
6  * <b>Copyright 2022 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 "sl_hal_dcdc_coulomb_counter.h"
32 #if defined(DCDC_COUNT) && (DCDC_COUNT > 0) && defined(DCDC_CCCTRL_CCEN)
33 #include "sl_assert.h"
34 #include "em_bus.h"
35 #include "em_prs.h"
36 
37 /***************************************************************************//**
38  * @addtogroup dcdccoulombcounter DCDC - DCDC Coulomb Counter Unit
39  * @brief DCDC Coulomb Counter (DCDC_COULOMB_COUNTER) Peripheral API
40  * @details
41  *  This module contains functions to control the DCDC_COULOMB_COUNTER peripheral
42  *  of Silicon Labs 32-bit MCUs and SoCs. The DCDC_COULOMB_COUNTER module
43  *  measures the charge delivered by the DC-DC.
44  * @{
45  ******************************************************************************/
46 
47 /* Global variable to store the channel assigned for calibrating */
48 /* the DCDC Coulomb Counter module. */
49 static int prs_channel = -1;
50 
51 /***************************************************************************//**
52  * Initializes DCDC_COULOMB_COUNTER module.
53  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_init(const sl_hal_dcdc_coulomb_counter_config_t * p_config)54 void sl_hal_dcdc_coulomb_counter_init(const sl_hal_dcdc_coulomb_counter_config_t *p_config)
55 {
56   if (DCDC->CCCTRL & _DCDC_CCCTRL_CCEN_MASK) {
57     /* Disable COULOMB_COUNTER_INTERNAL module. */
58     sl_hal_dcdc_coulomb_counter_disable();
59   }
60 
61   /* Set configuration. */
62   /* The counter thresholds can be used be used to establish a service  */
63   /* interval for the coulomb counter hardware. */
64   DCDC->CCTHR = ((uint32_t)(p_config->counter_threshold_em0) << _DCDC_CCTHR_EM0CNT_SHIFT)
65                 | ((uint32_t)(p_config->counter_threshold_em2) << _DCDC_CCTHR_EM2CNT_SHIFT);
66 }
67 
68 /***************************************************************************//**
69  * Disables DCDC_COULOMB_COUNTER module.
70  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_disable(void)71 void sl_hal_dcdc_coulomb_counter_disable(void)
72 {
73   /* Quick exit if the DCDC_COULOMB_COUNTER and is already disabled. */
74   if ((DCDC->CCCTRL & _DCDC_CCCTRL_CCEN_MASK) == 0U) {
75     return;
76   }
77 
78   /* Stop counting if running. */
79   if (DCDC->CCSTATUS & _DCDC_CCSTATUS_CCRUNNING_MASK) {
80     sl_hal_dcdc_coulomb_counter_stop();
81   }
82 
83   /* Disable module. */
84   EMU_DCDCSync(_DCDC_SYNCBUSY_MASK);
85   DCDC->CCCTRL_CLR = DCDC_CCCTRL_CCEN;
86 }
87 
88 /***************************************************************************//**
89  * Gets the DCDC_COULOMB_COUNTER count for the selected energy mode.
90  *
91  * @note As a guideline, using the default DC-DC settings, the DC-DC may
92  * produce up to 60,000-70,000 pulses per second for every mA of load current.
93  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_get_count(sl_hal_dcdc_coulomb_counter_emode_t emode)94 uint32_t sl_hal_dcdc_coulomb_counter_get_count(sl_hal_dcdc_coulomb_counter_emode_t emode)
95 {
96   if (emode == SL_HAL_DCDC_COULOMB_COUNTER_EM0) {
97     return DCDC->CCEM0CNT;
98   } else {
99     return DCDC->CCEM2CNT;
100   }
101 }
102 
103 /***************************************************************************//**
104  * Initializes the calibration of the DCDC Coulomb Counter.
105  *
106  * @note The coulomb counter should be calibrated to determine the
107  * charge per pulse, both for EM0/1 and for EM2/3 settings of the DC-DC.
108  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_cal_init(sl_hal_dcdc_coulomb_counter_calibration_config_t config)109 void sl_hal_dcdc_coulomb_counter_cal_init(sl_hal_dcdc_coulomb_counter_calibration_config_t config)
110 {
111   CMU_ClockEnable(cmuClock_PRS, true);
112 
113   if (prs_channel == -1) {
114     prs_channel = PRS_GetFreeChannel(prsTypeAsync);
115 
116     /* Channel number >= 0 if an unused PRS channel was found. */
117     /* If no free PRS channel was found then -1 is returned. */
118     if (prs_channel == -1) {
119       EFM_ASSERT(false);
120     }
121 
122     /* Configure an asynchronous PRS channel */
123     /* to route the DCDC MONO70NSANA producer to the CMU CALDN consumer. */
124     PRS_SourceAsyncSignalSet(prs_channel, PRS_ASYNC_CH_CTRL_SOURCESEL_DCDC, PRS_ASYNC_CH_CTRL_SIGSEL_DCDCMONO70NSANA);
125     PRS_ConnectConsumer(prs_channel, prsTypeAsync, prsConsumerCMU_CALDN);
126   }
127 
128   /* Setup the calibration circuit: */
129   /*  - HFXO is clocking up-counter. */
130   /*  - PRS CMU_CALDN consumer is clocking down-counter. */
131   CMU_CalibrateConfig(config.cal_count, cmuSelect_PRS, config.reference_clk);
132   CMU_CalibrateCont(false);
133 
134   /* Enable the desired calibration load. */
135   sl_hal_dcdc_coulomb_counter_set_cal_load_level(config.cal_emode, config.cal_load_level);
136   sl_hal_dcdc_coulomb_counter_enable_cal_load();
137 
138   /* Wait for at least one DC-DC pulse to settle DC-DC. */
139   DCDC->IF_CLR = DCDC_IF_REGULATION;
140   while ((DCDC->IF & DCDC_IF_REGULATION) == 0U) {
141     /* Wait for DCDC to complete it's startup. */
142   }
143 
144   /* Clear any pending calibration flags. */
145   CMU->IF_CLR = CMU_IF_CALRDY;
146   DCDC->CCCALCTRL_CLR = DCDC_CCCALCTRL_CCCALHALT;
147 }
148 
149 /***************************************************************************//**
150  * Starts DCDC_COULOMB_COUNTER calibration sequence.
151  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_cal_start(void)152 void sl_hal_dcdc_coulomb_counter_cal_start(void)
153 {
154   /* Start the up counter. */
155   CMU_CalibrateStart();
156 }
157 
158 /***************************************************************************//**
159  * Stops DCDC_COULOMB_COUNTER calibration sequence.
160  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_cal_stop(void)161 void sl_hal_dcdc_coulomb_counter_cal_stop(void)
162 {
163   CMU_CalibrateStop();
164 
165   sl_hal_dcdc_coulomb_counter_disable_cal_load();
166 }
167 
168 /***************************************************************************//**
169  * Sets the calibration load level.
170  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_set_cal_load_level(sl_hal_dcdc_coulomb_counter_emode_t emode,sl_hal_dcdc_coulomb_counter_calibration_load_level_t load_level)171 void sl_hal_dcdc_coulomb_counter_set_cal_load_level(sl_hal_dcdc_coulomb_counter_emode_t emode,
172                                                     sl_hal_dcdc_coulomb_counter_calibration_load_level_t load_level)
173 {
174   /* Set load level. */
175   DCDC->CCCALCTRL = (DCDC->CCCALCTRL & ~(_DCDC_CCCALCTRL_CCLVL_MASK))
176                     | ((load_level << _DCDC_CCCALCTRL_CCLVL_SHIFT) & _DCDC_CCCALCTRL_CCLVL_MASK);
177 
178   EMU_DCDCSync(_DCDC_SYNCBUSY_MASK);
179   /* Adjust DCDC when calibrating for EM2. */
180   if (emode == SL_HAL_DCDC_COULOMB_COUNTER_EM2) {
181     /* Forces DCDC to use EM23CTRL0.IPKVAL peak current settings in EM0. */
182     DCDC->CCCALCTRL_SET = DCDC_CCCALCTRL_CCCALEM2;
183   } else {
184     DCDC->CCCALCTRL_CLR = DCDC_CCCALCTRL_CCCALEM2;
185   }
186 }
187 
188 /***************************************************************************//**
189  * Enables the calibration load.
190  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_enable_cal_load(void)191 void sl_hal_dcdc_coulomb_counter_enable_cal_load(void)
192 {
193   DCDC->CCCALCTRL_SET = DCDC_CCCALCTRL_CCLOADEN;
194 }
195 
196 /***************************************************************************//**
197  * Disables the calibration load.
198  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_disable_cal_load(void)199 void sl_hal_dcdc_coulomb_counter_disable_cal_load(void)
200 {
201   DCDC->CCCALCTRL_CLR = DCDC_CCCALCTRL_CCLOADEN;
202 }
203 
204 /***************************************************************************//**
205  * Gets the calibration load current from the stored value
206  * in DEVINFO as measured during production testing.
207  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_get_cal_load_current(sl_hal_dcdc_coulomb_counter_calibration_load_level_t load_level)208 uint16_t sl_hal_dcdc_coulomb_counter_get_cal_load_current(sl_hal_dcdc_coulomb_counter_calibration_load_level_t load_level)
209 {
210   uint16_t ccload = 0U;
211 
212   switch (load_level) {
213     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD0:
214       ccload = (DEVINFO->CCLOAD10 & _DEVINFO_CCLOAD10_CCLOAD0_MASK)
215                >> _DEVINFO_CCLOAD10_CCLOAD0_SHIFT;
216       break;
217 
218     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD1:
219       ccload = (DEVINFO->CCLOAD10 & _DEVINFO_CCLOAD10_CCLOAD1_MASK)
220                >> _DEVINFO_CCLOAD10_CCLOAD1_SHIFT;
221       break;
222 
223     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD2:
224       ccload = (DEVINFO->CCLOAD32 & _DEVINFO_CCLOAD32_CCLOAD2_MASK)
225                >> _DEVINFO_CCLOAD32_CCLOAD2_SHIFT;
226       break;
227 
228     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD3:
229       ccload = (DEVINFO->CCLOAD32 & _DEVINFO_CCLOAD32_CCLOAD3_MASK)
230                >> _DEVINFO_CCLOAD32_CCLOAD3_SHIFT;
231       break;
232 
233     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD4:
234       ccload = (DEVINFO->CCLOAD54 & _DEVINFO_CCLOAD54_CCLOAD4_MASK)
235                >> _DEVINFO_CCLOAD54_CCLOAD4_SHIFT;
236       break;
237 
238     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD5:
239       ccload = (DEVINFO->CCLOAD54 & _DEVINFO_CCLOAD54_CCLOAD5_MASK)
240                >> _DEVINFO_CCLOAD54_CCLOAD5_SHIFT;
241       break;
242 
243     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD6:
244       ccload = (DEVINFO->CCLOAD76 & _DEVINFO_CCLOAD76_CCLOAD6_MASK)
245                >> _DEVINFO_CCLOAD76_CCLOAD6_SHIFT;
246       break;
247 
248     case SL_HAL_DCDC_COULOMB_COUNTER_CAL_LOAD7:
249       ccload = (DEVINFO->CCLOAD76 & _DEVINFO_CCLOAD76_CCLOAD7_MASK)
250                >> _DEVINFO_CCLOAD76_CCLOAD7_SHIFT;
251       break;
252 
253     default:
254       EFM_ASSERT(false);
255   }
256 
257   /* Make sure calibration load setting was measured during production test. */
258   EFM_ASSERT(ccload < (_DEVINFO_CCLOAD10_CCLOAD0_MASK >> _DEVINFO_CCLOAD10_CCLOAD0_SHIFT));
259 
260   return ccload;
261 }
262 
263 /***************************************************************************//**
264  * Gets the frequency (in Hz) of the CMU Calibration Up-Counter source.
265  *
266  * @note A high-frequency oscillator source of known frequency (fHF)
267  * is used to time pulses from the DC-DC and establish the charge per pulse.
268  ******************************************************************************/
sl_hal_dcdc_coulomb_counter_get_cal_reference_freq(void)269 uint32_t sl_hal_dcdc_coulomb_counter_get_cal_reference_freq(void)
270 {
271   uint32_t freq = 0U;
272   uint8_t upsel = _CMU_CALCTRL_UPSEL_DISABLED;
273 
274   upsel = (CMU->CALCTRL & _CMU_CALCTRL_UPSEL_MASK) >> _CMU_CALCTRL_UPSEL_SHIFT;
275 
276   switch (upsel) {
277     case _CMU_CALCTRL_UPSEL_PRS:
278       freq =  CMU_ClockFreqGet(cmuClock_PRS);
279       break;
280 
281     case _CMU_CALCTRL_UPSEL_HFXO:
282       freq  = SystemHFXOClockGet();
283       break;
284 
285     case _CMU_CALCTRL_UPSEL_LFXO:
286       freq = SystemLFXOClockGet();
287       break;
288 
289     case _CMU_CALCTRL_UPSEL_HFRCODPLL:
290       freq = SystemHFRCODPLLClockGet();
291       break;
292 
293     case _CMU_CALCTRL_UPSEL_FSRCO:
294       freq = SystemFSRCOClockGet();
295       break;
296 
297     case _CMU_CALCTRL_UPSEL_LFRCO:
298       freq = SystemLFRCOClockGet();
299       break;
300 
301     case _CMU_CALCTRL_UPSEL_ULFRCO:
302       freq = SystemULFRCOClockGet();
303       break;
304 
305     case _CMU_CALCTRL_UPSEL_DISABLED:
306     default:
307       EFM_ASSERT(false);
308   }
309 
310   return freq;
311 }
312 
313 /** @} (end addtogroup dcdccoulombcounter) */
314 #endif /* defined(DCDC_COUNT) && (DCDC_COUNT > 0) && defined(DCDC_CCCTRL_CCEN) */
315