forked
Diff: targets/TARGET_ONSEMI/TARGET_NCS36510/rtc.c
- Revision:
- 167:e84263d55307
- Parent:
- 150:02e0a0aed4ec
--- a/targets/TARGET_ONSEMI/TARGET_NCS36510/rtc.c Thu Jun 08 15:02:37 2017 +0100 +++ b/targets/TARGET_ONSEMI/TARGET_NCS36510/rtc.c Wed Jun 21 17:46:44 2017 +0100 @@ -7,11 +7,11 @@ * $Rev: 3525 $ * $Date: 2015-07-20 15:24:25 +0530 (Mon, 20 Jul 2015) $ ****************************************************************************** - * Copyright 2016 Semiconductor Components Industries LLC (d/b/a ON Semiconductor). + * Copyright 2016 Semiconductor Components Industries LLC (d/b/a �ON Semiconductor�). * All rights reserved. This software and/or documentation is licensed by ON Semiconductor * under limited terms and conditions. The terms and conditions pertaining to the software * and/or documentation are available at http://www.onsemi.com/site/pdf/ONSEMI_T&C.pdf - * (ON Semiconductor Standard Terms and Conditions of Sale, Section 8 Software) and + * (�ON Semiconductor Standard Terms and Conditions of Sale, Section 8 Software�) and * if applicable the software license agreement. Do not use this software and/or * documentation unless you have carefully read and you agree to the limited terms and * conditions. By using this software and/or documentation, you agree to the limited @@ -45,8 +45,25 @@ #include "mbed_assert.h" #include "lp_ticker_api.h" -static uint16_t SubSecond; -static uint64_t LastRtcTimeus; +static volatile uint64_t last_time_read; + +/** + * Convert sub seconds ticks to micro seconds. + * The clock running at 32kHz, a tick is 1/32768 of a second. + */ +static inline uint32_t ticks_to_us(uint16_t ticks) { + return (((uint64_t)ticks * RTC_SEC_TO_US) / RTC_CLOCK_HZ); +} + +/** + * Convert us into sub seconds ticks. + * @note result might be troncated to be in the range [0 - RTC_SUB_SEC_MASK]. + */ +static inline uint16_t us_to_ticks(uint32_t us) { + return (((uint64_t) us * RTC_CLOCK_HZ) / RTC_SEC_TO_US) & RTC_SUB_SEC_MASK; +} + +#define RTC_TICK_THRESHOLD 5 /* See rtc.h for details */ void fRtcInit(void) @@ -55,115 +72,131 @@ CLOCKREG->CCR.BITS.RTCEN = True; /* Enable RTC clock 32K */ /* Reset RTC control register */ - RTCREG->CONTROL.WORD = False; + RTCREG->CONTROL.WORD = 0; /* Initialize all counters */ - RTCREG->SECOND_COUNTER = False; - RTCREG->SUB_SECOND_COUNTER = False; - RTCREG->SECOND_ALARM = False; - RTCREG->SUB_SECOND_ALARM = False; - LastRtcTimeus = 0; + RTCREG->SECOND_COUNTER = 0; + RTCREG->SUB_SECOND_COUNTER = 0; + RTCREG->SECOND_ALARM = 0; + RTCREG->SUB_SECOND_ALARM = 0; + last_time_read = 0; /* Reset RTC Status register */ - RTCREG->STATUS.WORD = False; + RTCREG->STATUS.WORD = 0; /* Clear interrupt status */ - RTCREG->INT_CLEAR.WORD = False; + RTCREG->INT_CLEAR.WORD = ( + (1 << RTC_INT_CLR_SUB_SEC_BIT_POS) | + (1 << RTC_INT_CLR_SEC_BIT_POS) + ); + /* Wait previous write to complete */ + while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True); /* Start sec & sub_sec counter */ - while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True);/* Wait previous write to complete */ - RTCREG->CONTROL.WORD |= ((True << RTC_CONTROL_SUBSEC_CNT_START_BIT_POS) | - (True << RTC_CONTROL_SEC_CNT_START_BIT_POS)); + RTCREG->CONTROL.WORD |= ( + (True << RTC_CONTROL_SUBSEC_CNT_START_BIT_POS) | + (True << RTC_CONTROL_SEC_CNT_START_BIT_POS) + ); /* enable interruption associated with the rtc at NVIC level */ - NVIC_SetVector(Rtc_IRQn,(uint32_t)fRtcHandler); /* TODO define lp_ticker_isr */ + NVIC_SetVector(Rtc_IRQn,(uint32_t) fRtcHandler); /* TODO define lp_ticker_isr */ NVIC_ClearPendingIRQ(Rtc_IRQn); NVIC_EnableIRQ(Rtc_IRQn); - 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*/ - - return; + /* Wait for RTC to finish writing register */ + while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True); } /* See rtc.h for details */ void fRtcFree(void) { - /* Reset RTC control register */ - RTCREG->CONTROL.WORD = False; + /* Disable interrupts and counter */ + RTCREG->CONTROL.WORD = 0; /* disable interruption associated with the rtc */ NVIC_DisableIRQ(Rtc_IRQn); - 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*/ + /* Wait for RTC to finish writing register */ + while(RTCREG->STATUS.BITS.BSY_CTRL_REG_WRT == True); } /* See rtc.h for details */ void fRtcSetInterrupt(uint32_t timestamp) { - SubSecond = False; - uint32_t Second = False, EnableInterrupt = False; - uint8_t DividerAdjust = 1; - - if(timestamp) { - if(timestamp >= RTC_SEC_TO_US) { - /* TimeStamp is big enough to set second alarm */ - Second = ((timestamp / RTC_SEC_TO_US) & RTC_SEC_MASK); /* Convert micro second to second */ - RTCREG->SECOND_ALARM = Second; /* Write to alarm register */ + uint64_t current_time = fRtcRead(); - /* Enable second interrupt */ - EnableInterrupt = True << RTC_CONTROL_SEC_CNT_INT_BIT_POS; - } - timestamp = timestamp - Second * RTC_SEC_TO_US; /* Take out micro second for sub second alarm */ - if(timestamp > False) { - /* We have some thing for sub second */ + /* compute delta between current time and timestamp. + * Note: the current time used to compute the delta is relative (truncated + * to 32 bits). + */ + int32_t delta = timestamp - (uint32_t) current_time; + if (delta <= 0) { + // event considered in the past, set the interrupt as pending. + NVIC_SetPendingIRQ(Rtc_IRQn); + return; + } - /* Convert micro second to sub_seconds(each count = 30.5 us) */ - if(timestamp > 131000) { - DividerAdjust = 100; - } + uint64_t full_timestamp = (current_time & ~UINT32_MAX) | timestamp; + if ( (uint32_t)current_time > timestamp) { + full_timestamp += ((uint64_t) UINT32_MAX) + 1; + } - volatile uint64_t Temp = (timestamp / DividerAdjust * RTC_CLOCK_HZ); - Temp = (uint64_t)(Temp / RTC_SEC_TO_US * DividerAdjust); - SubSecond = Temp & RTC_SUB_SEC_MASK; + uint32_t target_seconds = full_timestamp / RTC_SEC_TO_US; + uint16_t target_ticks = us_to_ticks(full_timestamp); - if(SubSecond <= 5) { - SubSecond = 0; - } - - if(SubSecond > False) { - /* Second interrupt not enabled */ - - /* Set SUB SEC_ALARM */ - RTCREG->SUB_SECOND_ALARM = SubSecond; /* Write to sub second alarm */ + /* + * If the interrupt is in more than one second from now then use the + * second alarm, otherwise use the subsecond alarm. + * In case of the second alarm is used, there is no need to preserve the + * remaining subsecond because the irq handler should manage spurious + * interrupts (like when the timestamp is in the past). In such case, irq + * handler will schedule a new interrupt with the remaining us. + */ + NVIC_DisableIRQ(Rtc_IRQn); + if (target_seconds != RTCREG->SECOND_COUNTER) { + RTCREG->SECOND_ALARM = target_seconds; - /* Enable sub second interrupt */ - EnableInterrupt |= (True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); - } - } - - RTCREG->CONTROL.WORD |= EnableInterrupt; - /* Enable RTC interrupt */ - NVIC_EnableIRQ(Rtc_IRQn); - - /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/ - while((RTCREG->STATUS.WORD & ((True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) | - (True << RTC_STATUS_SEC_ALARM_WRT_BIT_POS) | - (True << RTC_STATUS_CONTROL_WRT_BIT_POS))) == True); - } - return; + uint32_t rtc_control = RTCREG->CONTROL.WORD; + rtc_control |= (1 << RTC_CONTROL_SEC_CNT_INT_BIT_POS); // enable seconds interrupt + rtc_control &= ~(1 << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); // disable sub sec interrupt + RTCREG->CONTROL.WORD = rtc_control; + } else { + uint16_t current_ticks = RTCREG->SUB_SECOND_COUNTER; + if (current_ticks == target_ticks || + ((target_ticks > current_ticks) && ((target_ticks - current_ticks) < RTC_TICK_THRESHOLD)) || + ((target_ticks < current_ticks) && ((RTC_SUB_SEC_MASK - (current_ticks - target_ticks)) < RTC_TICK_THRESHOLD))) { + // target ticks too close; schedule the interrupt immediately + NVIC_SetPendingIRQ(Rtc_IRQn); + } else { + RTCREG->SUB_SECOND_ALARM = target_ticks; + + uint32_t rtc_control = RTCREG->CONTROL.WORD; + rtc_control &= ~(1 << RTC_CONTROL_SEC_CNT_INT_BIT_POS); // disable seconds interrupt + rtc_control |= (1 << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); // enable sub sec interrupt + RTCREG->CONTROL.WORD = rtc_control; + } + } + NVIC_EnableIRQ(Rtc_IRQn); + + /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/ + while(RTCREG->STATUS.WORD & + ( + (True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) | + (True << RTC_STATUS_SEC_ALARM_WRT_BIT_POS) | + (True << RTC_STATUS_CONTROL_WRT_BIT_POS) + ) + ); } /* See rtc.h for details */ void fRtcDisableInterrupt(void) { - /* Disable RTC interrupt */ NVIC_DisableIRQ(Rtc_IRQn); } /* See rtc.h for details */ void fRtcEnableInterrupt(void) { - /* Enable RTC interrupt */ NVIC_EnableIRQ(Rtc_IRQn); } @@ -176,39 +209,36 @@ (True << RTC_INT_CLR_SEC_BIT_POS)); while((RTCREG->STATUS.WORD & ((True << RTC_STATUS_SUB_SEC_INT_CLR_WRT_BIT_POS) | - (True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS))) == True); /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/ + (True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS)))); /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/ } /* See rtc.h for details */ uint64_t fRtcRead(void) { - uint32_t Second; - uint16_t SubSecond; - /* Hardware Bug fix: The rollover of the sub-second counter initiates the increment of the second counter. * That means there is one cycle where the sub-second has rolled back to zero and the second counter has not incremented * and a read during that cycle will be incorrect. That will occur for one RTC cycle and that is about 31us of exposure. * If you read a zero in the sub-second counter then increment the second counter by 1. * Alternatively, subtract 1 from the Sub-seconds counter to align the Second and Sub-Second rollover. */ - - /* Read the Second and Sub-second counters, then read the Second counter again. - * If it changed, then the Second rolled over while reading Sub-seconds, so go back and read them both again. - */ + uint32_t seconds = RTCREG->SECOND_COUNTER; + uint16_t ticks = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK; - do { - Second = RTCREG->SECOND_COUNTER; /* Get SEC_COUNTER reg value */ - SubSecond = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK; /* Get SUB_SEC_COUNTER reg value */ - } while (Second != RTCREG->SECOND_COUNTER); /* Repeat if the second has changed */ + /* + * If seconds has changed while reading ticks, read them both again. + */ + while (seconds != RTCREG->SECOND_COUNTER) { + seconds = RTCREG->SECOND_COUNTER; + ticks = (RTCREG->SUB_SECOND_COUNTER - 1) & SUB_SEC_MASK; + } - //note: casting to float removed to avoid reduction in resolution - uint64_t RtcTimeus = ((uint64_t)SubSecond * RTC_SEC_TO_US / RTC_CLOCK_HZ) + ((uint64_t)Second * RTC_SEC_TO_US); + uint64_t current_time = ((uint64_t) seconds * RTC_SEC_TO_US) + ticks_to_us(ticks); /*check that the time did not go backwards */ - MBED_ASSERT(RtcTimeus >= LastRtcTimeus); - LastRtcTimeus = RtcTimeus; + MBED_ASSERT(current_time >= last_time_read); + last_time_read = current_time; - return RtcTimeus; + return current_time; } /* See rtc.h for details */ @@ -244,43 +274,31 @@ /* See rtc.h for details */ void fRtcHandler(void) { - /* SUB_SECOND/SECOND interrupt occured */ - volatile uint32_t TempStatus = RTCREG->STATUS.WORD; - /* Disable RTC interrupt */ NVIC_DisableIRQ(Rtc_IRQn); /* Clear sec & sub_sec interrupts */ - RTCREG->INT_CLEAR.WORD = ((True << RTC_INT_CLR_SUB_SEC_BIT_POS) | - (True << RTC_INT_CLR_SEC_BIT_POS)); + RTCREG->INT_CLEAR.WORD = ( + (True << RTC_INT_CLR_SUB_SEC_BIT_POS) | + (True << RTC_INT_CLR_SEC_BIT_POS) + ); - /* TODO ANDing SUB_SEC & SEC interrupt - work around for RTC issue - will be resolved in REV G */ - if(TempStatus & RTC_SEC_INT_STATUS_MASK) { - /* Second interrupt occured */ - if(SubSecond > False) { - /* Set SUB SEC_ALARM */ - RTCREG->SUB_SECOND_ALARM = SubSecond + RTCREG->SUB_SECOND_COUNTER; - /* Enable sub second interrupt */ - RTCREG->CONTROL.WORD |= (True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS); - } else { - /* We reach here after second interrupt is occured */ - RTCREG->CONTROL.WORD &= ~(True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) | - (True << RTC_CONTROL_SEC_CNT_INT_BIT_POS); - } - } else { - /* We reach here after sub_second or (Sub second + second) interrupt occured */ - /* Disable Second and sub_second interrupt */ - RTCREG->CONTROL.WORD &= ~(True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) | - (True << RTC_CONTROL_SEC_CNT_INT_BIT_POS); - } + /* Disable sub seconds and seconds interrupts */ + RTCREG->CONTROL.WORD &= ~( + (True << RTC_CONTROL_SUBSEC_CNT_INT_BIT_POS) | + (True << RTC_CONTROL_SEC_CNT_INT_BIT_POS) + ); NVIC_EnableIRQ(Rtc_IRQn); - /* Wait for RTC to finish writing register - RTC operates on 32K clock as compared to 32M core*/ - while((RTCREG->STATUS.WORD & ((True << RTC_STATUS_SUB_SEC_ALARM_WRT_BIT_POS) | - (True << RTC_STATUS_CONTROL_WRT_BIT_POS) | - (True << RTC_STATUS_SUB_SEC_INT_CLR_WRT_BIT_POS) | - (True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS))) == True); + /* Wait for RTC to finish writing registers */ + while(RTCREG->STATUS.WORD & + ( + (True << RTC_STATUS_CONTROL_WRT_BIT_POS) | + (True << RTC_STATUS_SUB_SEC_INT_CLR_WRT_BIT_POS) | + (True << RTC_STATUS_SEC_INT_CLR_WRT_BIT_POS) + ) + ); lp_ticker_irq_handler(); }