Skip to content

Commit aa6835a

Browse files
authored
Merge pull request #6048 from OpenNuvoton/nuvoton_ticker
Nuvoton: Rework us_ticker and lp_ticker
2 parents bc4dea2 + fae160f commit aa6835a

File tree

8 files changed

+1055
-784
lines changed

8 files changed

+1055
-784
lines changed

targets/TARGET_NUVOTON/TARGET_M451/lp_ticker.c

Lines changed: 139 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,38 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
1717
#include "lp_ticker_api.h"
1818

1919
#if DEVICE_LOWPOWERTIMER
2020

2121
#include "sleep_api.h"
22+
#include "mbed_wait_api.h"
23+
#include "mbed_assert.h"
2224
#include "nu_modutil.h"
2325
#include "nu_miscutil.h"
24-
#include "mbed_critical.h"
25-
26-
// lp_ticker tick = us = timestamp
27-
#define US_PER_TICK (1)
28-
#define US_PER_SEC (1000 * 1000)
2926

30-
#define US_PER_TMR2_INT (US_PER_SEC * 10)
31-
#define TMR2_CLK_PER_SEC (__LXT)
32-
#define TMR2_CLK_PER_TMR2_INT ((uint32_t) ((uint64_t) US_PER_TMR2_INT * TMR2_CLK_PER_SEC / US_PER_SEC))
33-
#define TMR3_CLK_PER_SEC (__LXT)
27+
/* Micro seconds per second */
28+
#define NU_US_PER_SEC 1000000
29+
/* Timer clock per lp_ticker tick */
30+
#define NU_TMRCLK_PER_TICK 1
31+
/* Timer clock per second */
32+
#define NU_TMRCLK_PER_SEC (__LXT)
33+
/* Timer max counter bit size */
34+
#define NU_TMR_MAXCNT_BITSIZE 24
35+
/* Timer max counter */
36+
#define NU_TMR_MAXCNT ((1 << NU_TMR_MAXCNT_BITSIZE) - 1)
3437

3538
static void tmr2_vec(void);
3639
static void tmr3_vec(void);
37-
static void lp_ticker_arm_cd(void);
40+
/* Configure scheduled alarm */
41+
static void arm_alarm(uint32_t cd_clk);
3842

39-
static int lp_ticker_inited = 0;
40-
static volatile uint32_t counter_major = 0;
41-
static volatile uint32_t cd_major_minor_clks = 0;
42-
static volatile uint32_t cd_minor_clks = 0;
43-
static volatile uint32_t wakeup_tick = (uint32_t) -1;
43+
static int ticker_inited = 0;
44+
static uint32_t ticker_last_read_clk = 0;
4445

45-
// NOTE: To wake the system from power down mode, timer clock source must be ether LXT or LIRC.
46-
// NOTE: TIMER_2 for normal counting and TIMER_3 for scheduled wakeup
46+
/* NOTE: To wake the system from power down mode, timer clock source must be ether LXT or LIRC. */
47+
/* NOTE: TIMER_2 for normal counting and TIMER_3 for scheduled alarm */
4748
static const struct nu_modinit_s timer2_modinit = {TIMER_2, TMR2_MODULE, CLK_CLKSEL1_TMR2SEL_LXT, 0, TMR2_RST, TMR2_IRQn, (void *) tmr2_vec};
4849
static const struct nu_modinit_s timer3_modinit = {TIMER_3, TMR3_MODULE, CLK_CLKSEL1_TMR3SEL_LXT, 0, TMR3_RST, TMR3_IRQn, (void *) tmr3_vec};
4950

@@ -52,20 +53,17 @@ static const struct nu_modinit_s timer3_modinit = {TIMER_3, TMR3_MODULE, CLK_CLK
5253

5354
void lp_ticker_init(void)
5455
{
55-
if (lp_ticker_inited) {
56+
if (ticker_inited) {
5657
return;
5758
}
58-
lp_ticker_inited = 1;
59-
60-
counter_major = 0;
61-
cd_major_minor_clks = 0;
62-
cd_minor_clks = 0;
63-
wakeup_tick = (uint32_t) -1;
59+
ticker_inited = 1;
60+
61+
ticker_last_read_clk = 0;
6462

6563
// Reset module
6664
SYS_ResetModule(timer2_modinit.rsetidx);
6765
SYS_ResetModule(timer3_modinit.rsetidx);
68-
66+
6967
// Select IP clock source
7068
CLK_SetModuleClock(timer2_modinit.clkidx, timer2_modinit.clksrc, timer2_modinit.clkdiv);
7169
CLK_SetModuleClock(timer3_modinit.clkidx, timer3_modinit.clksrc, timer3_modinit.clkdiv);
@@ -75,93 +73,114 @@ void lp_ticker_init(void)
7573

7674
// Configure clock
7775
uint32_t clk_timer2 = TIMER_GetModuleClock((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
78-
uint32_t prescale_timer2 = clk_timer2 / TMR2_CLK_PER_SEC - 1;
76+
uint32_t prescale_timer2 = clk_timer2 / NU_TMRCLK_PER_SEC - 1;
7977
MBED_ASSERT((prescale_timer2 != (uint32_t) -1) && prescale_timer2 <= 127);
80-
MBED_ASSERT((clk_timer2 % TMR2_CLK_PER_SEC) == 0);
81-
uint32_t cmp_timer2 = TMR2_CLK_PER_TMR2_INT;
78+
MBED_ASSERT((clk_timer2 % NU_TMRCLK_PER_SEC) == 0);
79+
uint32_t cmp_timer2 = TMR_CMP_MAX;
8280
MBED_ASSERT(cmp_timer2 >= TMR_CMP_MIN && cmp_timer2 <= TMR_CMP_MAX);
8381
// Continuous mode
8482
// NOTE: TIMER_CTL_CNTDATEN_Msk exists in NUC472, but not in M451. In M451, TIMER_CNT is updated continuously by default.
8583
((TIMER_T *) NU_MODBASE(timer2_modinit.modname))->CTL = TIMER_PERIODIC_MODE | prescale_timer2/* | TIMER_CTL_CNTDATEN_Msk*/;
8684
((TIMER_T *) NU_MODBASE(timer2_modinit.modname))->CMP = cmp_timer2;
87-
85+
8886
// Set vector
8987
NVIC_SetVector(timer2_modinit.irq_n, (uint32_t) timer2_modinit.var);
9088
NVIC_SetVector(timer3_modinit.irq_n, (uint32_t) timer3_modinit.var);
91-
89+
9290
NVIC_EnableIRQ(timer2_modinit.irq_n);
9391
NVIC_EnableIRQ(timer3_modinit.irq_n);
94-
92+
9593
TIMER_EnableInt((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
9694
TIMER_EnableWakeup((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
97-
98-
// NOTE: TIMER_Start() first and then lp_ticker_set_interrupt(); otherwise, we may get stuck in lp_ticker_read() because
99-
// timer is not running.
100-
101-
// Start timer
95+
/* NOTE: When engine is clocked by low power clock source (LXT/LIRC), we need to wait for 3 engine clocks. */
96+
wait_us((NU_US_PER_SEC / NU_TMRCLK_PER_SEC) * 3);
10297
TIMER_Start((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
103-
104-
// Schedule wakeup to match semantics of lp_ticker_get_compare_match()
105-
lp_ticker_set_interrupt(wakeup_tick);
10698
}
10799

108100
timestamp_t lp_ticker_read()
109-
{
110-
if (! lp_ticker_inited) {
101+
{
102+
if (! ticker_inited) {
111103
lp_ticker_init();
112104
}
113-
105+
114106
TIMER_T * timer2_base = (TIMER_T *) NU_MODBASE(timer2_modinit.modname);
115-
116-
do {
117-
uint64_t major_minor_clks;
118-
uint32_t minor_clks;
119-
120-
// NOTE: As TIMER_CNT = TIMER_CMP and counter_major has increased by one, TIMER_CNT doesn't change to 0 for one tick time.
121-
// NOTE: As TIMER_CNT = TIMER_CMP or TIMER_CNT = 0, counter_major (ISR) may not sync with TIMER_CNT. So skip and fetch stable one at the cost of 1 clock delay on this read.
122-
do {
123-
core_util_critical_section_enter();
124-
125-
// NOTE: Order of reading minor_us/carry here is significant.
126-
minor_clks = TIMER_GetCounter(timer2_base);
127-
uint32_t carry = (timer2_base->INTSTS & TIMER_INTSTS_TIF_Msk) ? 1 : 0;
128-
// When TIMER_CNT approaches TIMER_CMP and will wrap soon, we may get carry but TIMER_CNT not wrapped. Hanlde carefully carry == 1 && TIMER_CNT is near TIMER_CMP.
129-
if (carry && minor_clks > (TMR2_CLK_PER_TMR2_INT / 2)) {
130-
major_minor_clks = (counter_major + 1) * TMR2_CLK_PER_TMR2_INT;
131-
}
132-
else {
133-
major_minor_clks = (counter_major + carry) * TMR2_CLK_PER_TMR2_INT + minor_clks;
134-
}
135-
136-
core_util_critical_section_exit();
137-
}
138-
while (minor_clks == 0 || minor_clks == TMR2_CLK_PER_TMR2_INT);
139-
140-
// Add power-down compensation
141-
return ((uint64_t) major_minor_clks * US_PER_SEC / TMR2_CLK_PER_SEC / US_PER_TICK);
142-
}
143-
while (0);
107+
108+
ticker_last_read_clk = TIMER_GetCounter(timer2_base);
109+
return (ticker_last_read_clk / NU_TMRCLK_PER_TICK);
144110
}
145111

146112
void lp_ticker_set_interrupt(timestamp_t timestamp)
147113
{
148-
uint32_t delta = timestamp - lp_ticker_read();
149-
wakeup_tick = timestamp;
150-
151114
TIMER_Stop((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
115+
116+
/* We need to get alarm interval from alarm timestamp `timestamp` to configure H/W timer.
117+
*
118+
* Because both `timestamp` and xx_ticker_read() would wrap around, we have difficulties in distinguishing
119+
* long future event and past event. To distinguish them, we need `tick_last_read` against which
120+
* `timestamp` is calculated out. In timeline, we would always have below after fixing wrap-around:
121+
* (1) tick_last_read <= present_clk
122+
* (2) tick_last_read <= alarm_ts_clk
123+
*
124+
*
125+
* 1. Future event case:
126+
*
127+
* tick_last_read present_clk alarm_ts_clk
128+
* | | |
129+
* --------------------------------------------------------
130+
* |-alarm_intvl1_clk-|
131+
* |-------------------alarm_intvl2_clk-------------------|
132+
*
133+
* 2. Past event case:
134+
*
135+
* tick_last_read alarm_ts_clk present_clk
136+
* | | |
137+
* --------------------------------------------------------
138+
* |-------------------alarm_intvl1_clk-------------------|
139+
* |-alarm_intvl2_clk-|
140+
*
141+
* Unfortunately, `tick_last_read` is not passed along the xx_ticker_set_interrupt() call. To solve it, we
142+
* assume that `tick_last_read` tick is exactly the one returned by the last xx_ticker_read() call before
143+
* xx_ticker_set_interrupt() is invoked. With this assumption, we can hold it via `xx_ticker_last_read_clk`
144+
* in xx_ticker_read().
145+
*/
146+
147+
/* ticker_last_read_clk will update in lp_ticker_read(). Keep it beforehand. */
148+
uint32_t last_read_clk = ticker_last_read_clk;
149+
uint32_t present_clk = lp_ticker_read() * NU_TMRCLK_PER_TICK;
150+
uint32_t alarm_ts_clk = timestamp * NU_TMRCLK_PER_TICK;
151+
uint32_t alarm_intvl1_clk, alarm_intvl2_clk;
152152

153-
cd_major_minor_clks = (uint64_t) delta * US_PER_TICK * TMR3_CLK_PER_SEC / US_PER_SEC;
154-
lp_ticker_arm_cd();
155-
}
153+
/* alarm_intvl1_clk = present_clk - last_read_clk
154+
*
155+
* NOTE: Don't miss the `=` sign here. Otherwise, we would get the wrong result.
156+
*/
157+
if (present_clk >= last_read_clk) {
158+
alarm_intvl1_clk = present_clk - last_read_clk;
159+
} else {
160+
alarm_intvl1_clk = (uint32_t) (((uint64_t) NU_TMR_MAXCNT) + 1 + present_clk - last_read_clk);
161+
}
156162

157-
void lp_ticker_fire_interrupt(void)
158-
{
159-
cd_major_minor_clks = cd_minor_clks = 0;
160-
/**
161-
* This event was in the past. Set the interrupt as pending, but don't process it here.
162-
* This prevents a recurive loop under heavy load which can lead to a stack overflow.
163-
*/
164-
NVIC_SetPendingIRQ(timer3_modinit.irq_n);
163+
/* alarm_intvl2_clk = alarm_ts_clk - last_read_clk
164+
*
165+
* NOTE: Don't miss the `=` sign here. Otherwise, we would get the wrong result.
166+
*/
167+
if (alarm_ts_clk >= last_read_clk) {
168+
alarm_intvl2_clk = alarm_ts_clk - last_read_clk;
169+
} else {
170+
alarm_intvl2_clk = (uint32_t) (((uint64_t) NU_TMR_MAXCNT) + 1 + alarm_ts_clk - last_read_clk);
171+
}
172+
173+
/* Distinguish (long) future event and past event
174+
*
175+
* NOTE: No '=' sign here. Alarm should go off immediately in equal case.
176+
*/
177+
if (alarm_intvl2_clk > alarm_intvl1_clk) {
178+
/* Schedule for future event */
179+
arm_alarm(alarm_intvl2_clk - alarm_intvl1_clk);
180+
} else {
181+
/* Go off immediately for past event, including equal case */
182+
lp_ticker_fire_interrupt();
183+
}
165184
}
166185

167186
void lp_ticker_disable_interrupt(void)
@@ -174,48 +193,64 @@ void lp_ticker_clear_interrupt(void)
174193
TIMER_ClearIntFlag((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
175194
}
176195

196+
void lp_ticker_fire_interrupt(void)
197+
{
198+
// NOTE: This event was in the past. Set the interrupt as pending, but don't process it here.
199+
// This prevents a recursive loop under heavy load which can lead to a stack overflow.
200+
NVIC_SetPendingIRQ(timer3_modinit.irq_n);
201+
}
202+
177203
static void tmr2_vec(void)
178204
{
179205
TIMER_ClearIntFlag((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
180206
TIMER_ClearWakeupFlag((TIMER_T *) NU_MODBASE(timer2_modinit.modname));
181-
counter_major ++;
182207
}
183208

184209
static void tmr3_vec(void)
185210
{
186211
TIMER_ClearIntFlag((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
187212
TIMER_ClearWakeupFlag((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
188-
cd_major_minor_clks = (cd_major_minor_clks > cd_minor_clks) ? (cd_major_minor_clks - cd_minor_clks) : 0;
189-
if (cd_major_minor_clks == 0) {
190-
// NOTE: lp_ticker_set_interrupt() may get called in lp_ticker_irq_handler();
191-
lp_ticker_irq_handler();
192-
}
193-
else {
194-
lp_ticker_arm_cd();
195-
}
213+
214+
// NOTE: lp_ticker_set_interrupt() may get called in lp_ticker_irq_handler();
215+
lp_ticker_irq_handler();
216+
196217
}
197218

198-
static void lp_ticker_arm_cd(void)
219+
static void arm_alarm(uint32_t cd_clk)
199220
{
200221
TIMER_T * timer3_base = (TIMER_T *) NU_MODBASE(timer3_modinit.modname);
201-
222+
202223
// Reset 8-bit PSC counter, 24-bit up counter value and CNTEN bit
203224
timer3_base->CTL |= TIMER_CTL_RSTCNT_Msk;
204225
// One-shot mode, Clock = 1 KHz
205226
uint32_t clk_timer3 = TIMER_GetModuleClock((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
206-
uint32_t prescale_timer3 = clk_timer3 / TMR3_CLK_PER_SEC - 1;
227+
uint32_t prescale_timer3 = clk_timer3 / NU_TMRCLK_PER_SEC - 1;
207228
MBED_ASSERT((prescale_timer3 != (uint32_t) -1) && prescale_timer3 <= 127);
208-
MBED_ASSERT((clk_timer3 % TMR3_CLK_PER_SEC) == 0);
229+
MBED_ASSERT((clk_timer3 % NU_TMRCLK_PER_SEC) == 0);
209230
// NOTE: TIMER_CTL_CNTDATEN_Msk exists in NUC472, but not in M451. In M451, TIMER_CNT is updated continuously by default.
210231
timer3_base->CTL &= ~(TIMER_CTL_OPMODE_Msk | TIMER_CTL_PSC_Msk/* | TIMER_CTL_CNTDATEN_Msk*/);
211232
timer3_base->CTL |= TIMER_ONESHOT_MODE | prescale_timer3/* | TIMER_CTL_CNTDATEN_Msk*/;
212-
213-
cd_minor_clks = cd_major_minor_clks;
214-
cd_minor_clks = NU_CLAMP(cd_minor_clks, TMR_CMP_MIN, TMR_CMP_MAX);
215-
timer3_base->CMP = cd_minor_clks;
216-
233+
234+
/* NOTE: Because H/W timer requests min compare value, our implementation would have alarm delay of
235+
* (TMR_CMP_MIN - interval_clk) clocks when interval_clk is between [1, TMR_CMP_MIN). */
236+
uint32_t cmp_timer3 = cd_clk;
237+
cmp_timer3 = NU_CLAMP(cmp_timer3, TMR_CMP_MIN, TMR_CMP_MAX);
238+
timer3_base->CMP = cmp_timer3;
239+
217240
TIMER_EnableInt(timer3_base);
218241
TIMER_EnableWakeup((TIMER_T *) NU_MODBASE(timer3_modinit.modname));
242+
/* NOTE: When engine is clocked by low power clock source (LXT/LIRC), we need to wait for 3 engine clocks. */
243+
wait_us((NU_US_PER_SEC / NU_TMRCLK_PER_SEC) * 3);
219244
TIMER_Start(timer3_base);
220245
}
246+
247+
const ticker_info_t* lp_ticker_get_info()
248+
{
249+
static const ticker_info_t info = {
250+
NU_TMRCLK_PER_SEC / NU_TMRCLK_PER_TICK,
251+
NU_TMR_MAXCNT_BITSIZE
252+
};
253+
return &info;
254+
}
255+
221256
#endif

0 commit comments

Comments
 (0)