1 /***************************************************************************//**
2  * @file
3  * @brief SLEEPTIMER hardware abstraction implementation for RTCC.
4  *******************************************************************************
5  * # License
6  * <b>Copyright 2019 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_rtcc.h"
32 #include "sl_sleeptimer.h"
33 #include "sli_sleeptimer_hal.h"
34 #include "em_core.h"
35 #include "em_cmu.h"
36 
37 #if SL_SLEEPTIMER_PERIPHERAL == SL_SLEEPTIMER_PERIPHERAL_RTCC
38 
39 // Minimum difference between current count value and what the comparator of the timer can be set to.
40 // 1 tick is added to the minimum diff for the algorithm of compensation for the IRQ handler that
41 // triggers when CNT == compare_value + 1. For more details refer to sleeptimer_hal_set_compare() function's header.
42 #define SLEEPTIMER_COMPARE_MIN_DIFF  (2 + 1)
43 
44 #define SLEEPTIMER_TMR_WIDTH (_RTCC_CNT_MASK)
45 
46 static bool cc_disabled = true;
47 
48 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
49                                        uint32_t b);
50 
51 /******************************************************************************
52  * Initializes RTCC sleep timer.
53  *****************************************************************************/
sleeptimer_hal_init_timer(void)54 void sleeptimer_hal_init_timer(void)
55 {
56   RTCC_Init_TypeDef rtcc_init   = RTCC_INIT_DEFAULT;
57   RTCC_CCChConf_TypeDef channel = RTCC_CH_INIT_COMPARE_DEFAULT;
58 
59   CMU_ClockEnable(cmuClock_RTCC, true);
60 
61   rtcc_init.enable = false;
62   rtcc_init.presc = (RTCC_CntPresc_TypeDef)(CMU_PrescToLog2(SL_SLEEPTIMER_FREQ_DIVIDER - 1));
63 #if (SL_SLEEPTIMER_DEBUGRUN == 1)
64   rtcc_init.debugRun = true;
65 #endif
66 
67   RTCC_Init(&rtcc_init);
68 
69   // Compare channel starts disabled and is enabled only when compare match interrupt is enabled.
70   channel.chMode = rtccCapComChModeOff;
71   RTCC_ChannelInit(1u, &channel);
72 
73   RTCC_IntDisable(_RTCC_IEN_MASK);
74   RTCC_IntClear(_RTCC_IF_MASK);
75   RTCC_CounterSet(0u);
76 
77   RTCC_Enable(true);
78 
79   NVIC_ClearPendingIRQ(RTCC_IRQn);
80   NVIC_EnableIRQ(RTCC_IRQn);
81 }
82 
83 /******************************************************************************
84  * Gets RTCC counter value.
85  *****************************************************************************/
sleeptimer_hal_get_counter(void)86 uint32_t sleeptimer_hal_get_counter(void)
87 {
88   return RTCC_CounterGet();
89 }
90 
91 /******************************************************************************
92  * Gets RTCC compare value.
93  *****************************************************************************/
sleeptimer_hal_get_compare(void)94 uint32_t sleeptimer_hal_get_compare(void)
95 {
96   return RTCC_ChannelCCVGet(1u);
97 }
98 
99 /******************************************************************************
100  * Sets RTCC compare value.
101  *
102  * @note Compare match value is set to the requested value - 1. This is done
103  * to compensate for the fact that the RTCC compare match interrupt always
104  * triggers at the end of the requested ticks and that the IRQ handler is
105  * executed when current tick count == compare_value + 1.
106  *****************************************************************************/
sleeptimer_hal_set_compare(uint32_t value)107 void sleeptimer_hal_set_compare(uint32_t value)
108 {
109   CORE_DECLARE_IRQ_STATE;
110   uint32_t counter;
111   uint32_t compare;
112   uint32_t compare_value = value;
113 
114   CORE_ENTER_CRITICAL();
115   counter = sleeptimer_hal_get_counter();
116   compare = sleeptimer_hal_get_compare();
117   if (((RTCC_IntGet() & RTCC_IEN_CC1) != 0)
118       || get_time_diff(compare, counter) > SLEEPTIMER_COMPARE_MIN_DIFF
119       || compare == counter) {
120     // Add margin if necessary
121     if (get_time_diff(compare_value, counter) < SLEEPTIMER_COMPARE_MIN_DIFF) {
122       compare_value = counter + SLEEPTIMER_COMPARE_MIN_DIFF;
123     }
124     compare_value %= SLEEPTIMER_TMR_WIDTH;
125 
126     RTCC_ChannelCCVSet(1u, compare_value - 1u);
127     sleeptimer_hal_enable_int(SLEEPTIMER_EVENT_COMP);
128   }
129   CORE_EXIT_CRITICAL();
130 
131   if (cc_disabled) {
132     RTCC->CC[1].CTRL |= RTCC_CC_CTRL_MODE_OUTPUTCOMPARE;
133     cc_disabled = false;
134   }
135 }
136 
137 /******************************************************************************
138  * Enables RTCC interrupts.
139  *****************************************************************************/
sleeptimer_hal_enable_int(uint8_t local_flag)140 void sleeptimer_hal_enable_int(uint8_t local_flag)
141 {
142   uint32_t rtcc_ien = 0u;
143 
144   if (local_flag & SLEEPTIMER_EVENT_OF) {
145     rtcc_ien |= RTCC_IEN_OF;
146   }
147 
148   if (local_flag & SLEEPTIMER_EVENT_COMP) {
149     rtcc_ien |= RTCC_IEN_CC1;
150   }
151 
152   RTCC_IntEnable(rtcc_ien);
153 }
154 
155 /******************************************************************************
156  * Disables RTCC interrupts.
157  *****************************************************************************/
sleeptimer_hal_disable_int(uint8_t local_flag)158 void sleeptimer_hal_disable_int(uint8_t local_flag)
159 {
160   uint32_t rtcc_int_dis = 0u;
161 
162   if (local_flag & SLEEPTIMER_EVENT_OF) {
163     rtcc_int_dis |= RTCC_IEN_OF;
164   }
165 
166   if (local_flag & SLEEPTIMER_EVENT_COMP) {
167     rtcc_int_dis |= RTCC_IEN_CC1;
168 
169     cc_disabled = true;
170     RTCC->CC[1].CTRL &= ~_RTCC_CC_CTRL_MODE_MASK;
171   }
172 
173   RTCC_IntDisable(rtcc_int_dis);
174 }
175 
176 /*******************************************************************************
177  * Hardware Abstraction Layer to set timer interrupts.
178  ******************************************************************************/
sleeptimer_hal_set_int(uint8_t local_flag)179 void sleeptimer_hal_set_int(uint8_t local_flag)
180 {
181   if (local_flag & SLEEPTIMER_EVENT_COMP) {
182     RTCC_IntSet(RTCC_IF_CC1);
183   }
184 }
185 
186 /******************************************************************************
187  * Gets status of specified interrupt.
188  *
189  * Note: This function must be called with interrupts disabled.
190  *****************************************************************************/
sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)191 bool sli_sleeptimer_hal_is_int_status_set(uint8_t local_flag)
192 {
193   bool int_is_set = false;
194   uint32_t irq_flag = RTCC_IntGet();
195 
196   switch (local_flag) {
197     case SLEEPTIMER_EVENT_COMP:
198       int_is_set = ((irq_flag & RTCC_IF_CC1) == RTCC_IF_CC1);
199       break;
200 
201     case SLEEPTIMER_EVENT_OF:
202       int_is_set = ((irq_flag & RTCC_IF_OF) == RTCC_IF_OF);
203       break;
204 
205     default:
206       break;
207   }
208 
209   return int_is_set;
210 }
211 
212 /*******************************************************************************
213  * RTCC interrupt handler.
214  ******************************************************************************/
RTCC_IRQHandler(void)215 void RTCC_IRQHandler(void)
216 {
217   CORE_DECLARE_IRQ_STATE;
218   uint8_t local_flag = 0;
219   uint32_t irq_flag;
220 
221   CORE_ENTER_ATOMIC();
222   irq_flag = RTCC_IntGet();
223 
224   if (irq_flag & RTCC_IF_OF) {
225     local_flag |= SLEEPTIMER_EVENT_OF;
226   }
227   if (irq_flag & RTCC_IF_CC1) {
228     local_flag |= SLEEPTIMER_EVENT_COMP;
229   }
230   RTCC_IntClear(irq_flag & (RTCC_IF_OF | RTCC_IF_CC1 | RTCC_IF_CC0));
231 
232   process_timer_irq(local_flag);
233 
234   CORE_EXIT_ATOMIC();
235 }
236 
237 /*******************************************************************************
238  * Gets RTCC timer frequency.
239  ******************************************************************************/
sleeptimer_hal_get_timer_frequency(void)240 uint32_t sleeptimer_hal_get_timer_frequency(void)
241 {
242   return (CMU_ClockFreqGet(cmuClock_RTCC) >> (CMU_PrescToLog2(SL_SLEEPTIMER_FREQ_DIVIDER - 1)));
243 }
244 
245 /*******************************************************************************
246  * Computes difference between two times taking into account timer wrap-around.
247  *
248  * @param a Time.
249  * @param b Time to substract from a.
250  *
251  * @return Time difference.
252  ******************************************************************************/
get_time_diff(uint32_t a,uint32_t b)253 __STATIC_INLINE uint32_t get_time_diff(uint32_t a,
254                                        uint32_t b)
255 {
256   return (a - b);
257 }
258 
259 #endif
260