Skip to content

Commit 045b026

Browse files
committed
NCS36510 RTC driver: Fix driver.
* Initialization clear interrupt status * Remove state in management of interrupt * Handle timestamp in the past * Handle current seconds, even if out of the relative timestamp. * Simplify interrupt handling logic.
1 parent c6433b0 commit 045b026

File tree

1 file changed

+135
-117
lines changed
  • targets/TARGET_ONSEMI/TARGET_NCS36510

1 file changed

+135
-117
lines changed

targets/TARGET_ONSEMI/TARGET_NCS36510/rtc.c

Lines changed: 135 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,25 @@
4545
#include "mbed_assert.h"
4646
#include "lp_ticker_api.h"
4747

48-
static uint16_t SubSecond;
49-
static uint64_t LastRtcTimeus;
48+
static volatile uint64_t last_time_read;
49+
50+
/**
51+
* Convert sub seconds ticks to micro seconds.
52+
* The clock running at 32kHz, a tick is 1/32768 of a second.
53+
*/
54+
static inline uint32_t ticks_to_us(uint16_t ticks) {
55+
return (((uint64_t)ticks * RTC_SEC_TO_US) / RTC_CLOCK_HZ);
56+
}
57+
58+
/**
59+
* Convert us into sub seconds ticks.
60+
* @note result might be troncated to be in the range [0 - RTC_SUB_SEC_MASK].
61+
*/
62+
static inline uint16_t us_to_ticks(uint32_t us) {
63+
return (((uint64_t) us * RTC_CLOCK_HZ) / RTC_SEC_TO_US) & RTC_SUB_SEC_MASK;
64+
}
65+
66+
#define RTC_TICK_THRESHOLD 5
5067

5168
/* See rtc.h for details */
5269
void fRtcInit(void)
@@ -55,115 +72,131 @@ void fRtcInit(void)
5572
CLOCKREG->CCR.BITS.RTCEN = True; /* Enable RTC clock 32K */
5673

5774
/* Reset RTC control register */
58-
RTCREG->CONTROL.WORD = False;
75+
RTCREG->CONTROL.WORD = 0;
5976

6077
/* Initialize all counters */
61-
RTCREG->SECOND_COUNTER = False;
62-
RTCREG->SUB_SECOND_COUNTER = False;
63-
RTCREG->SECOND_ALARM = False;
64-
RTCREG->SUB_SECOND_ALARM = False;
65-
LastRtcTimeus = 0;
78+
RTCREG->SECOND_COUNTER = 0;
79+
RTCREG->SUB_SECOND_COUNTER = 0;
80+
RTCREG->SECOND_ALARM = 0;
81+
RTCREG->SUB_SECOND_ALARM = 0;
82+
last_time_read = 0;
6683

6784
/* Reset RTC Status register */
68-
RTCREG->STATUS.WORD = False;
85+
RTCREG->STATUS.WORD = 0;
6986

7087
/* Clear interrupt status */
71-
RTCREG->INT_CLEAR.WORD = False;
88+
RTCREG->INT_CLEAR.WORD = (
89+
(1 << RTC_INT_CLR_SUB_SEC_BIT_POS) |
90+
(1 << RTC_INT_CLR_SEC_BIT_POS)
91+
);
7292

93+
/* Wait previous write to complete */
94+
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True);
7395
/* Start sec & sub_sec counter */
74-
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True);/* Wait previous write to complete */
75-
RTCREG->CONTROL.WORD |= ((True << RTC_CONTROL_SUBSEC_CNT_START_BIT_POS) |
76-
(True << RTC_CONTROL_SEC_CNT_START_BIT_POS));
96+
RTCREG->CONTROL.WORD |= (
97+
(True << RTC_CONTROL_SUBSEC_CNT_START_BIT_POS) |
98+
(True << RTC_CONTROL_SEC_CNT_START_BIT_POS)
99+
);
77100

78101
/* enable interruption associated with the rtc at NVIC level */
79-
NVIC_SetVector(Rtc_IRQn,(uint32_t)fRtcHandler); /* TODO define lp_ticker_isr */
102+
NVIC_SetVector(Rtc_IRQn,(uint32_t) fRtcHandler); /* TODO define lp_ticker_isr */
80103
NVIC_ClearPendingIRQ(Rtc_IRQn);
81104
NVIC_EnableIRQ(Rtc_IRQn);
82105

83-
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True); /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/
84-
85-
return;
106+
/* Wait for RTC to finish writing register */
107+
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True);
86108
}
87109

88110
/* See rtc.h for details */
89111
void fRtcFree(void)
90112
{
91-
/* Reset RTC control register */
92-
RTCREG->CONTROL.WORD = False;
113+
/* Disable interrupts and counter */
114+
RTCREG->CONTROL.WORD = 0;
93115

94116
/* disable interruption associated with the rtc */
95117
NVIC_DisableIRQ(Rtc_IRQn);
96118

97-
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True); /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/
119+
/* Wait for RTC to finish writing register */
120+
while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True);
98121
}
99122

100123
/* See rtc.h for details */
101124
void fRtcSetInterrupt(uint32_t timestamp)
102125
{
103-
SubSecond = False;
104-
uint32_t Second = False, EnableInterrupt = False;
105-
uint8_t DividerAdjust = 1;
106-
107-
if(timestamp) {
108-
if(timestamp >= RTC_SEC_TO_US) {
109-
/* TimeStamp is big enough to set second alarm */
110-
Second = ((timestamp / RTC_SEC_TO_US) & RTC_SEC_MASK); /* Convert micro second to second */
111-
RTCREG->SECOND_ALARM = Second; /* Write to alarm register */
112-
113-
/* Enable second interrupt */
114-
EnableInterrupt = True << RTC_CONTROL_SEC_CNT_INT_BIT_POS;
115-
}
116-
timestamp = timestamp - Second * RTC_SEC_TO_US; /* Take out micro second for sub second alarm */
117-
if(timestamp > False) {
118-
/* We have some thing for sub second */
119-
120-
/* Convert micro second to sub_seconds(each count = 30.5 us) */
121-
if(timestamp > 131000) {
122-
DividerAdjust = 100;
123-
}
124-
125-
volatile uint64_t Temp = (timestamp / DividerAdjust * RTC_CLOCK_HZ);
126-
Temp = (uint64_t)(Temp / RTC_SEC_TO_US * DividerAdjust);
127-
SubSecond = Temp & RTC_SUB_SEC_MASK;
128-
129-
if(SubSecond <= 5) {
130-
SubSecond = 0;
131-
}
132-
133-
if(SubSecond > False) {
134-
/* Second interrupt not enabled */
135-
136-
/* Set SUB SEC_ALARM */
137-
RTCREG->SUB_SECOND_ALARM = SubSecond; /* Write to sub second alarm */
138-
139-
/* Enable sub second interrupt */
140-
EnableInterrupt |= (True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS);
141-
}
142-
}
143-
144-
RTCREG->CONTROL.WORD |= EnableInterrupt;
145-
/* Enable RTC interrupt */
146-
NVIC_EnableIRQ(Rtc_IRQn);
147-
148-
/* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/
149-
while((RTCREG->STATUS.WORD & ((True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) |
150-
(True << RTC_STATUS_SEC_ALARM_WRT_BIT_POS) |
151-
(True << RTC_STATUS_CONTROL_WRT_BIT_POS))));
152-
}
153-
return;
126+
uint64_t current_time = fRtcRead();
127+
128+
/* compute delta between current time and timestamp.
129+
* Note: the current time used to compute the delta is relative (truncated
130+
* to 32 bits).
131+
*/
132+
int32_t delta = timestamp - (uint32_t) current_time;
133+
if (delta <= 0) {
134+
// event considered in the past, set the interrupt as pending.
135+
NVIC_SetPendingIRQ(Rtc_IRQn);
136+
return;
137+
}
138+
139+
uint64_t full_timestamp = (current_time & ~UINT32_MAX) | timestamp;
140+
if ( (uint32_t)current_time > timestamp) {
141+
full_timestamp += ((uint64_t) UINT32_MAX) + 1;
142+
}
143+
144+
uint32_t target_seconds = full_timestamp / RTC_SEC_TO_US;
145+
uint16_t target_ticks = us_to_ticks(full_timestamp);
146+
147+
/*
148+
* If the interrupt is in more than one second from now then use the
149+
* second alarm, otherwise use the subsecond alarm.
150+
* In case of the second alarm is used, there is no need to preserve the
151+
* remaining subsecond because the irq handler should manage spurious
152+
* interrupts (like when the timestamp is in the past). In such case, irq
153+
* handler will schedule a new interrupt with the remaining us.
154+
*/
155+
NVIC_DisableIRQ(Rtc_IRQn);
156+
if (target_seconds != RTCREG->SECOND_COUNTER) {
157+
RTCREG->SECOND_ALARM = target_seconds;
158+
159+
uint32_t rtc_control = RTCREG->CONTROL.WORD;
160+
rtc_control |= (1 << RTC_CONTROL_SEC_CNT_INT_BIT_POS); // enable seconds interrupt
161+
rtc_control &= ~(1 << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); // disable sub sec interrupt
162+
RTCREG->CONTROL.WORD = rtc_control;
163+
} else {
164+
uint16_t current_ticks = RTCREG->SUB_SECOND_COUNTER;
165+
if (current_ticks == target_ticks ||
166+
((target_ticks > current_ticks) && ((target_ticks - current_ticks) < RTC_TICK_THRESHOLD)) ||
167+
((target_ticks < current_ticks) && ((RTC_SUB_SEC_MASK - (current_ticks - target_ticks)) < RTC_TICK_THRESHOLD))) {
168+
// target ticks too close; schedule the interrupt immediately
169+
NVIC_SetPendingIRQ(Rtc_IRQn);
170+
} else {
171+
RTCREG->SUB_SECOND_ALARM = target_ticks;
172+
173+
uint32_t rtc_control = RTCREG->CONTROL.WORD;
174+
rtc_control &= ~(1 << RTC_CONTROL_SEC_CNT_INT_BIT_POS); // disable seconds interrupt
175+
rtc_control |= (1 << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); // enable sub sec interrupt
176+
RTCREG->CONTROL.WORD = rtc_control;
177+
}
178+
}
179+
NVIC_EnableIRQ(Rtc_IRQn);
180+
181+
/* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/
182+
while(RTCREG->STATUS.WORD &
183+
(
184+
(True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) |
185+
(True << RTC_STATUS_SEC_ALARM_WRT_BIT_POS) |
186+
(True << RTC_STATUS_CONTROL_WRT_BIT_POS)
187+
)
188+
);
154189
}
155190

156191
/* See rtc.h for details */
157192
void fRtcDisableInterrupt(void)
158193
{
159-
/* Disable RTC interrupt */
160194
NVIC_DisableIRQ(Rtc_IRQn);
161195
}
162196

163197
/* See rtc.h for details */
164198
void fRtcEnableInterrupt(void)
165199
{
166-
/* Enable RTC interrupt */
167200
NVIC_EnableIRQ(Rtc_IRQn);
168201
}
169202

@@ -182,33 +215,30 @@ void fRtcClearInterrupt(void)
182215
/* See rtc.h for details */
183216
uint64_t fRtcRead(void)
184217
{
185-
uint32_t Second;
186-
uint16_t SubSecond;
187-
188218
/* Hardware Bug fix: The rollover of the sub-second counter initiates the increment of the second counter.
189219
* That means there is one cycle where the sub-second has rolled back to zero and the second counter has not incremented
190220
* and a read during that cycle will be incorrect. That will occur for one RTC cycle and that is about 31us of exposure.
191221
* If you read a zero in the sub-second counter then increment the second counter by 1.
192222
* Alternatively, subtract 1 from the Sub-seconds counter to align the Second and Sub-Second rollover.
193223
*/
224+
uint32_t seconds = RTCREG->SECOND_COUNTER;
225+
uint16_t ticks = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK;
194226

195-
/* Read the Second and Sub-second counters, then read the Second counter again.
196-
* If it changed, then the Second rolled over while reading Sub-seconds, so go back and read them both again.
227+
/*
228+
* If seconds has changed while reading ticks, read them both again.
197229
*/
230+
while (seconds != RTCREG->SECOND_COUNTER) {
231+
seconds = RTCREG->SECOND_COUNTER;
232+
ticks = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK;
233+
}
198234

199-
do {
200-
Second = RTCREG->SECOND_COUNTER; /* Get SEC_COUNTER reg value */
201-
SubSecond = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK; /* Get SUB_SEC_COUNTER reg value */
202-
} while (Second != RTCREG->SECOND_COUNTER); /* Repeat if the second has changed */
203-
204-
//note: casting to float removed to avoid reduction in resolution
205-
uint64_t RtcTimeus = ((uint64_t)SubSecond * RTC_SEC_TO_US / RTC_CLOCK_HZ) + ((uint64_t)Second * RTC_SEC_TO_US);
235+
uint64_t current_time = ((uint64_t) seconds * RTC_SEC_TO_US) + ticks_to_us(ticks);
206236

207237
/*check that the time did not go backwards */
208-
MBED_ASSERT(RtcTimeus >= LastRtcTimeus);
209-
LastRtcTimeus = RtcTimeus;
238+
MBED_ASSERT(current_time >= last_time_read);
239+
last_time_read = current_time;
210240

211-
return RtcTimeus;
241+
return current_time;
212242
}
213243

214244
/* See rtc.h for details */
@@ -244,43 +274,31 @@ void fRtcWrite(uint64_t RtcTimeus)
244274
/* See rtc.h for details */
245275
void fRtcHandler(void)
246276
{
247-
/* SUB_SECOND/SECOND interrupt occured */
248-
volatile uint32_t TempStatus = RTCREG->STATUS.WORD;
249-
250277
/* Disable RTC interrupt */
251278
NVIC_DisableIRQ(Rtc_IRQn);
252279

253280
/* Clear sec & sub_sec interrupts */
254-
RTCREG->INT_CLEAR.WORD = ((True << RTC_INT_CLR_SUB_SEC_BIT_POS) |
255-
(True << RTC_INT_CLR_SEC_BIT_POS));
281+
RTCREG->INT_CLEAR.WORD = (
282+
(True << RTC_INT_CLR_SUB_SEC_BIT_POS) |
283+
(True << RTC_INT_CLR_SEC_BIT_POS)
284+
);
256285

257-
/* TODO ANDing SUB_SEC & SEC interrupt - work around for RTC issue - will be resolved in REV G */
258-
if(TempStatus & RTC_SEC_INT_STATUS_MASK) {
259-
/* Second interrupt occured */
260-
if(SubSecond > False) {
261-
/* Set SUB SEC_ALARM */
262-
RTCREG->SUB_SECOND_ALARM = SubSecond + RTCREG->SUB_SECOND_COUNTER;
263-
/* Enable sub second interrupt */
264-
RTCREG->CONTROL.WORD |= (True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS);
265-
} else {
266-
/* We reach here after second interrupt is occured */
267-
RTCREG->CONTROL.WORD &= ~(True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) |
268-
(True << RTC_CONTROL_SEC_CNT_INT_BIT_POS);
269-
}
270-
} else {
271-
/* We reach here after sub_second or (Sub second + second) interrupt occured */
272-
/* Disable Second and sub_second interrupt */
273-
RTCREG->CONTROL.WORD &= ~(True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) |
274-
(True << RTC_CONTROL_SEC_CNT_INT_BIT_POS);
275-
}
286+
/* Disable sub seconds and seconds interrupts */
287+
RTCREG->CONTROL.WORD &= ~(
288+
(True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) |
289+
(True << RTC_CONTROL_SEC_CNT_INT_BIT_POS)
290+
);
276291

277292
NVIC_EnableIRQ(Rtc_IRQn);
278293

279-
/* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/
280-
while((RTCREG->STATUS.WORD & ((True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) |
281-
(True << RTC_STATUS_CONTROL_WRT_BIT_POS) |
282-
(True << RTC_STATUS_SUB_SEC_INT_CLR_WRT_BIT_POS) |
283-
(True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS))));
294+
/* Wait for RTC to finish writing registers */
295+
while(RTCREG->STATUS.WORD &
296+
(
297+
(True << RTC_STATUS_CONTROL_WRT_BIT_POS) |
298+
(True << RTC_STATUS_SUB_SEC_INT_CLR_WRT_BIT_POS) |
299+
(True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS)
300+
)
301+
);
284302

285303
lp_ticker_irq_handler();
286304
}

0 commit comments

Comments
 (0)