1 //*****************************************************************************
2 //
3 //! @file am_hal_wdt.c
4 //!
5 //! @brief Hardware abstraction layer for the Watchdog Timer module.
6 //!
7 //! @addtogroup wdt3p WDT - Watchdog Timer
8 //! @ingroup apollo3p_hal
9 //! @{
10 //
11 //*****************************************************************************
12 
13 //*****************************************************************************
14 //
15 // Copyright (c) 2024, Ambiq Micro, Inc.
16 // All rights reserved.
17 //
18 // Redistribution and use in source and binary forms, with or without
19 // modification, are permitted provided that the following conditions are met:
20 //
21 // 1. Redistributions of source code must retain the above copyright notice,
22 // this list of conditions and the following disclaimer.
23 //
24 // 2. Redistributions in binary form must reproduce the above copyright
25 // notice, this list of conditions and the following disclaimer in the
26 // documentation and/or other materials provided with the distribution.
27 //
28 // 3. Neither the name of the copyright holder nor the names of its
29 // contributors may be used to endorse or promote products derived from this
30 // software without specific prior written permission.
31 //
32 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
36 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 // POSSIBILITY OF SUCH DAMAGE.
43 //
44 // This is part of revision release_sdk_3_2_0-dd5f40c14b of the AmbiqSuite Development Package.
45 //
46 //*****************************************************************************
47 
48 #include <stdint.h>
49 #include <stdbool.h>
50 #include "am_mcu_apollo.h"
51 
52 //*****************************************************************************
53 //
54 // Adjacency check
55 //
56 // This is related to the timer read workaround. This macro checks to see if
57 // the two supplied count values are within one "tick" of eachother. It should
58 // still pass in the event of a timer rollover. The "B" read is assumed to
59 // follow the "A" read.  The macro returns "TRUE" when the adjacent timer reads
60 // can be used.
61 //
62 //*****************************************************************************
63 #define adjacent(A, B)      (((A) == (B)) || (((A) + 1) == (B)) || ((B) == 0))
64 
65 //*****************************************************************************
66 //
67 // @brief Configure the watchdog timer.
68 //
69 // @param psConfig - pointer to a configuration structure containing the
70 // desired watchdog settings.
71 //
72 // This function will set the watchdog configuration register based on the
73 // user's desired settings listed in the structure referenced by psConfig. If
74 // the structure indicates that watchdog interrupts are desired, this function
75 // will also set the interrupt enable bit in the configuration register.
76 //
77 // @note In order to actually receive watchdog interrupt and/or watchdog reset
78 // events, the caller will also need to make sure that the watchdog interrupt
79 // vector is enabled in the ARM NVIC, and that watchdog resets are enabled in
80 // the reset generator module. Otherwise, the watchdog-generated interrupt and
81 // reset events will have no effect.
82 //
83 //*****************************************************************************
84 void
am_hal_wdt_init(const am_hal_wdt_config_t * psConfig)85 am_hal_wdt_init(const am_hal_wdt_config_t *psConfig)
86 {
87     uint32_t ui32ConfigVal;
88     uint16_t ui16IntCount, ui16ResetCount;
89     bool bResetEnabled = psConfig->ui32Config & AM_HAL_WDT_ENABLE_RESET;
90     bool bInterruptEnabled = psConfig->ui32Config & AM_HAL_WDT_ENABLE_INTERRUPT;
91 
92     //
93     // Read the desired settings from the psConfig structure.
94     //
95     ui16IntCount = psConfig->ui16InterruptCount;
96     ui16ResetCount = psConfig->ui16ResetCount;
97 
98     //
99     // Write the interrupt and reset count values to a temporary variable.
100     //
101     // Accept the passed Config value, but clear the Counts that we are about to set.
102     ui32ConfigVal = psConfig->ui32Config & ~(WDT_CFG_INTVAL_Msk | WDT_CFG_RESVAL_Msk);
103     ui32ConfigVal |= _VAL2FLD(WDT_CFG_INTVAL, ui16IntCount);
104     ui32ConfigVal |= _VAL2FLD(WDT_CFG_RESVAL, ui16ResetCount);
105 
106     //
107     // If interrupts should be enabled, set the appropriate bit in the
108     // temporary variable. Also, enable the interrupt in INTEN register in the
109     // watchdog module.
110     //
111     if ( bInterruptEnabled )
112     {
113         //
114         // Enable the watchdog interrupt if the configuration calls for them.
115         //
116         WDT->INTEN |= WDT_INTEN_WDTINT_Msk;
117     }
118     else
119     {
120         //
121         // Disable the watchdog interrupt if the configuration doesn't call for
122         // watchdog interrupts.
123         //
124         WDT->INTEN &= ~WDT_INTEN_WDTINT_Msk;
125     }
126 
127     //
128     // If resets should be enabled, set the appropriate bit in the temporary
129     // variable.
130     //
131     if ( bResetEnabled )
132     {
133         //
134         // Also enable watchdog resets in the reset module.
135         //
136         RSTGEN->CFG |= RSTGEN_CFG_WDREN_Msk;
137     }
138     else
139     {
140         //
141         // Disable watchdog resets in the reset module.
142         //
143         RSTGEN->CFG &= ~RSTGEN_CFG_WDREN_Msk;
144     }
145 
146     //
147     // Check for a user specified clock select. If none specified then
148     // set 128Hz.
149     //
150     if ( !(psConfig->ui32Config & WDT_CFG_CLKSEL_Msk) )
151     {
152         ui32ConfigVal |= _VAL2FLD(WDT_CFG_CLKSEL, WDT_CFG_CLKSEL_128HZ);
153     }
154 
155     //
156     // Write the saved value to the watchdog configuration register.
157     //
158     WDT->CFG = ui32ConfigVal;
159 } // am_hal_wdt_init()
160 
161 //*****************************************************************************
162 //
163 // @brief Starts the watchdog timer.
164 //
165 // Enables the watchdog timer tick using the 'enable' bit in the watchdog
166 // configuration register.  This function does not perform any locking of the
167 // watchdog timer, so it can be disabled or reconfigured later.
168 //
169 //*****************************************************************************
170 void
am_hal_wdt_start(void)171 am_hal_wdt_start(void)
172 {
173     //
174     // Make sure the watchdog timer is in the "reset" state, and then set the
175     // enable bit to start counting.
176     //
177     WDT->CFG   |= WDT_CFG_WDTEN_Msk;
178     WDT->RSTRT  = WDT_RSTRT_RSTRT_KEYVALUE;
179 } // am_hal_wdt_start()
180 
181 //*****************************************************************************
182 //
183 // @brief Stops the watchdog timer.
184 //
185 // Disables the watchdog timer tick by clearing the 'enable' bit in the
186 // watchdog configuration register.
187 //
188 //*****************************************************************************
189 void
am_hal_wdt_halt(void)190 am_hal_wdt_halt(void)
191 {
192     //
193     // Clear the watchdog enable bit.
194     //
195     WDT->CFG &= ~WDT_CFG_WDTEN_Msk;
196 } // am_hal_wdt_halt()
197 
198 //*****************************************************************************
199 //
200 // @brief Locks the watchdog configuration and starts the watchdog timer.
201 //
202 // This function sets the watchdog "lock" register, which prevents software
203 // from re-configuring the watchdog. This action will also set the enable bit
204 // for the watchdog timer, so it will start counting immediately.
205 //
206 //*****************************************************************************
207 void
am_hal_wdt_lock_and_start(void)208 am_hal_wdt_lock_and_start(void)
209 {
210     //
211     // Write the 'key' value to the watchdog lock register.
212     //
213     WDT->LOCK = WDT_LOCK_LOCK_KEYVALUE;
214 } // am_hal_wdt_lock_and_start()
215 
216 //*****************************************************************************
217 //
218 // @brief Read the state of the wdt interrupt status.
219 //
220 // @param bEnabledOnly - return the status of only the enabled interrupts.
221 //
222 // This function extracts the interrupt status bits and returns the enabled or
223 // raw based on bEnabledOnly.
224 //
225 // @return WDT interrupt status.
226 //
227 //*****************************************************************************
228 uint32_t
am_hal_wdt_int_status_get(bool bEnabledOnly)229 am_hal_wdt_int_status_get(bool bEnabledOnly)
230 {
231     if ( bEnabledOnly )
232     {
233         uint32_t ui32RetVal;
234         AM_CRITICAL_BEGIN
235         ui32RetVal = WDT->INTSTAT;
236         ui32RetVal &= WDT->INTEN;
237         AM_CRITICAL_END
238         return ui32RetVal;
239     }
240     else
241     {
242         return WDT->INTSTAT;
243     }
244 } // am_hal_wdt_int_status_get()
245 
246 //*****************************************************************************
247 //
248 // @brief Set the state of the wdt interrupt status bit.
249 //
250 // This function sets the interrupt bit.
251 //
252 //*****************************************************************************
253 void
am_hal_wdt_int_set(void)254 am_hal_wdt_int_set(void)
255 {
256     WDT->INTSET = WDT_INTSET_WDTINT_Msk;
257 } // am_hal_wdt_int_set()
258 
259 //*****************************************************************************
260 //
261 // @brief Clear the state of the wdt interrupt status bit.
262 //
263 // This function clear the interrupt bit.
264 //
265 //*****************************************************************************
266 void
am_hal_wdt_int_clear(void)267 am_hal_wdt_int_clear(void)
268 {
269     WDT->INTCLR = WDT_INTCLR_WDTINT_Msk;
270 } // am_hal_wdt_int_clear()
271 
272 //*****************************************************************************
273 //
274 // @brief Enable the wdt interrupt.
275 //
276 // This function enable the interrupt.
277 //
278 //*****************************************************************************
279 void
am_hal_wdt_int_enable(void)280 am_hal_wdt_int_enable(void)
281 {
282     WDT->INTEN |= WDT_INTEN_WDTINT_Msk;
283 } // am_hal_wdt_int_enable()
284 
285 //*****************************************************************************
286 //
287 // @brief Return the enabled WDT interrupts.
288 //
289 // This function returns the enabled WDT interrupts.
290 //
291 // @return enabled WDT interrupts.
292 //
293 //*****************************************************************************
294 uint32_t
am_hal_wdt_int_enable_get(void)295 am_hal_wdt_int_enable_get(void)
296 {
297     return WDT->INTEN;
298 } // am_hal_wdt_int_enable_get()
299 
300 //*****************************************************************************
301 //
302 // @brief Disable the wdt interrupt.
303 //
304 // This function disablee the interrupt.
305 //
306 //*****************************************************************************
307 void
am_hal_wdt_int_disable(void)308 am_hal_wdt_int_disable(void)
309 {
310     WDT->INTEN &= ~WDT_INTEN_WDTINT_Msk;
311 } // am_hal_wdt_int_disable()
312 
313 //*****************************************************************************
314 //
315 // @brief Get the wdt counter value.
316 //
317 // This function reads the current value of watch dog timer counter register.
318 //
319 // @note WARNING caller is responsible for masking interrupts before calling this
320 // function.
321 //
322 //*****************************************************************************
323 uint32_t
am_hal_wdt_counter_get(void)324 am_hal_wdt_counter_get(void)
325 {
326     uint32_t ui32Values[3] = {0};
327     uint32_t ui32Value;
328 
329     //
330     // First, go read the value from the counter register 3 times
331     // back to back in assembly language.
332     //
333     am_hal_triple_read( AM_REGADDR(WDT, COUNT), ui32Values );
334 
335     //
336     // Mask out the COUNT field from the 3 read values.
337     //
338     ui32Values[0] = _VAL2FLD(WDT_COUNT_COUNT, ui32Values[0]);
339     ui32Values[1] = _VAL2FLD(WDT_COUNT_COUNT, ui32Values[1]);
340     ui32Values[2] = _VAL2FLD(WDT_COUNT_COUNT, ui32Values[2]);
341 
342     //
343     // Now, we'll figure out which of the three values is the correct time.
344     //
345     if (ui32Values[0] == ui32Values[1])
346     {
347         //
348         // If the first two values match, then neither one was a bad read.
349         // We'll take this as the current time.
350         //
351         ui32Value = ui32Values[1];
352     }
353     else
354     {
355         //
356         // If the first two values didn't match, then one of them might be bad.
357         // If one of the first two values is bad, then the third one should
358         // always be correct. We'll take the third value as the correct count.
359         //
360         ui32Value = ui32Values[2];
361 
362         //
363         // If all of the statements about the architecture are true, the third
364         // value should be correct, and it should always be within one count of
365         // either the first or the second value.
366         //
367         // Just in case, we'll check against the previous two values to make
368         // sure that our final answer was reasonable. If it isn't, we will
369         // flag it as a "bad read", and fail this assert statement.
370         //
371         // This shouldn't ever happen, and it hasn't ever happened in any of
372         // our tests so far.
373         //
374         am_hal_debug_assert_msg((adjacent(ui32Values[1], ui32Values[2]) ||
375                                  adjacent(ui32Values[0], ui32Values[2])),
376                                 "Bad CDT read");
377     }
378 
379     return ui32Value;
380 } // am_hal_wdt_counter_get()
381 
382 //*****************************************************************************
383 //
384 // End Doxygen group.
385 //! @}
386 //
387 //*****************************************************************************
388