mirror of https://github.com/ARMmbed/mbed-os.git
349 lines
11 KiB
C
349 lines
11 KiB
C
/* mbed Microcontroller Library
|
|
* Copyright (c) 2016 u-blox
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* The LP Ticker performs two functions for mbed:
|
|
*
|
|
* 1. Allows tracking of the passage of time.
|
|
* 2. Allows the system to enter the lowest power
|
|
* state for a given time.
|
|
*
|
|
* For this to work the single RTC interrupt needs
|
|
* to perform two functions. It needs to increment
|
|
* an overflow counter at every 32-bit overflow without
|
|
* otherwise affecting the system state (i.e. not waking it
|
|
* up and not putting it to sleep) and, when requested,
|
|
* it *also* needs to wake the system up from sleep
|
|
* at a specific time. Note also that the units of time
|
|
* from an mbed perspective are useconds, whereas the RTC
|
|
* is clocked at 32 kHz, hence there is conversion to be done.
|
|
*
|
|
* Since it is not possible to reset the RTC, we maintain
|
|
* a 32-bit window on it, starting at g_last_32bit_overflow_value
|
|
* and ending at g_next_32bit_overflow_value. All values
|
|
* fed back up to mbed are relative to g_last_32bit_overflow_value.
|
|
*/
|
|
|
|
#include "lp_ticker_api.h"
|
|
#include "sleep_api.h"
|
|
#include "mbed_critical.h"
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MACROS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* The maximum value of the RTC (48 bits) */
|
|
#define RTC_MAX 0x0000FFFFFFFFFFFFULL
|
|
|
|
/* RTC modulo */
|
|
#define RTC_MODULO (RTC_MAX + 1)
|
|
|
|
/* The 32-bit overflow value */
|
|
#define MODULO_32BIT 0x100000000ULL
|
|
|
|
/* Macro to increment a 64-bit RTC value x by y, with wrap */
|
|
#define INCREMENT_MOD(x, y) (x = ((uint64_t) x + (uint64_t) y) % RTC_MODULO)
|
|
|
|
/* Macro to get MSBs from a 64-bit integer */
|
|
#define MSBS(x) ((uint32_t) ((uint64_t) (x) >> 32))
|
|
|
|
/* Macro to get LSBs from a 64-bit integer */
|
|
#define LSBS(x) ((uint32_t) (x))
|
|
|
|
/* ----------------------------------------------------------------
|
|
* TYPES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* ----------------------------------------------------------------
|
|
* GLOBAL VARIABLES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* Incremented each time the RTC goes over 32 bits */
|
|
static uint32_t g_overflow_count = 0;
|
|
|
|
/* Set when a user interrupt has been requested but an overflow
|
|
* interrupt needs to happen first */
|
|
static bool g_user_interrupt_pending = false;
|
|
|
|
/* Set when a user interrupt is the next interrupt to happen */
|
|
static bool g_user_interrupt_set = false;
|
|
|
|
/* Initialised flag, used to protect against interrupts going
|
|
* off before we're initialised */
|
|
static bool g_initialised = false;
|
|
|
|
/* The next overflow value to be used */
|
|
static uint64_t g_next_32bit_overflow_value;
|
|
|
|
/* The next match-compare value to be used */
|
|
static uint64_t g_next_compare_value;
|
|
|
|
/* Keep track of the previous 32-bit overflow
|
|
* value so that we can report 32-bit time
|
|
* correctly */
|
|
static uint64_t g_last_32bit_overflow_value;
|
|
|
|
/* ----------------------------------------------------------------
|
|
* FUNCTION PROTOTYPES
|
|
* ----------------------------------------------------------------*/
|
|
|
|
static void set_interrupt_to_32bit_overflow(void);
|
|
static void set_interrupt_to_user_value(void);
|
|
|
|
/* ----------------------------------------------------------------
|
|
* STATIC FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* Convert a tick value (32,768 Hz) into a microsecond value */
|
|
static inline uint32_t ticksToUSeconds(uint32_t x)
|
|
{
|
|
/* TODO: find a way to avoid the multiply by 1000000
|
|
* Shift by 20 would introduce a 5% error, which is
|
|
* probably too much */
|
|
uint64_t result = ((((uint64_t) x) * 1000000) >> 15);
|
|
|
|
if (result > 0xFFFFFFFF) {
|
|
result = 0xFFFFFFFF;
|
|
}
|
|
|
|
return (uint32_t) result;
|
|
}
|
|
|
|
/* Convert a microsecond value into a tick value (32,768 Hz) */
|
|
static inline uint32_t uSecondsToTicks(uint32_t x)
|
|
{
|
|
/* TODO: find a way to avoid the divide by 1000000
|
|
* Shift by 20 would introduce a 5% error, which is
|
|
* probably too much */
|
|
return (uint32_t) ((((uint64_t) x) << 15) / 1000000);
|
|
}
|
|
|
|
/* Take g_next_32bit_overflow_value and apply it to g_next_compare_value and
|
|
* then the chip registers
|
|
* NOTE: the RTC interrupt should be disabled when calling this function */
|
|
static inline void set_interrupt_to_32bit_overflow()
|
|
{
|
|
/* Load up the values */
|
|
g_next_compare_value = g_next_32bit_overflow_value;
|
|
|
|
/* Set up the match register values */
|
|
RTC_IRQ_TIME_MSBS = MSBS(g_next_compare_value);
|
|
RTC_IRQ_TIME_LSBS = LSBS(g_next_compare_value);
|
|
}
|
|
|
|
/* Take g_next_compare_value and apply it to the chip registers
|
|
* NOTE: the RTC interrupt should be disabled when calling this function */
|
|
static inline void set_interrupt_to_user_value()
|
|
{
|
|
g_user_interrupt_set = true;
|
|
|
|
/* Write MSBS first, then the value is latched on LSBS write */
|
|
RTC_IRQ_TIME_MSBS = MSBS(g_next_compare_value);
|
|
RTC_IRQ_TIME_LSBS = LSBS(g_next_compare_value);
|
|
}
|
|
|
|
/* Get the RTC value
|
|
* NOTE: the RTC interrupt should be disabled when calling this function */
|
|
static inline uint64_t get_rtc_value()
|
|
{
|
|
uint64_t rtc_value;
|
|
|
|
rtc_value = ((uint64_t) RTC_TIME_MSBS) << 32;
|
|
rtc_value |= RTC_TIME_LSBS;
|
|
|
|
return rtc_value;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* NON-API FUNCTIONS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* RTC handler */
|
|
void IRQ0_RTC_Handler(void)
|
|
{
|
|
/* Have seen this interrupt occurring before initialisation, so guard
|
|
* against that */
|
|
if (g_initialised) {
|
|
if (g_user_interrupt_pending) {
|
|
/* If there was a user interrupt pending, set it now */
|
|
set_interrupt_to_user_value();
|
|
|
|
/* Reset the pending flag */
|
|
g_user_interrupt_pending = false;
|
|
|
|
/* This must have been a 32-bit overflow interrupt so
|
|
* increment the count */
|
|
g_overflow_count++;
|
|
g_last_32bit_overflow_value = g_next_32bit_overflow_value;
|
|
INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
|
|
} else {
|
|
if (g_user_interrupt_set) {
|
|
/* It's a user interrupt, so wake from sleep but don't
|
|
* increment the overflow count as this is not an
|
|
* overflow interrupt */
|
|
|
|
/* Reset the user interrupt flag and call mbed */
|
|
g_user_interrupt_set = false;
|
|
lp_ticker_irq_handler();
|
|
} else {
|
|
/* Increment the count as this was a 32-bit overflow
|
|
* interrupt rather than a user interrupt */
|
|
g_overflow_count++;
|
|
g_last_32bit_overflow_value = g_next_32bit_overflow_value;
|
|
INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
|
|
}
|
|
|
|
/* Set the next interrupt to be at the 32-bit overflow */
|
|
set_interrupt_to_32bit_overflow();
|
|
}
|
|
}
|
|
|
|
/* Clear the interrupt */
|
|
RTC_IRQ_CLR = 0xFFFFFFFF;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* MBED API CALLS
|
|
* ----------------------------------------------------------------*/
|
|
|
|
/* This will be called once at start of day to get the RTC running */
|
|
void lp_ticker_init(void)
|
|
{
|
|
if (!g_initialised) {
|
|
/* Reset the overflow count and the flags */
|
|
g_overflow_count = 0;
|
|
g_user_interrupt_pending = false;
|
|
g_user_interrupt_set = false;
|
|
|
|
/* Setup the next natural 32-bit overflow value */
|
|
g_next_32bit_overflow_value = get_rtc_value();
|
|
g_last_32bit_overflow_value = g_next_32bit_overflow_value;
|
|
INCREMENT_MOD(g_next_32bit_overflow_value, MODULO_32BIT);
|
|
|
|
/* Clear the interrupt */
|
|
RTC_IRQ_CLR = 0xFFFFFFFF;
|
|
|
|
/* Interrupt at 32-bit overflow */
|
|
set_interrupt_to_32bit_overflow();
|
|
|
|
/* Enable the interrupt */
|
|
g_initialised = true;
|
|
NVIC_EnableIRQ(RTC_IRQn);
|
|
}
|
|
}
|
|
|
|
uint32_t lp_ticker_read(void)
|
|
{
|
|
uint64_t rtcNow;
|
|
|
|
/* Disable interrupts to avoid collisions */
|
|
core_util_critical_section_enter();
|
|
|
|
/* Just in case this is called before initialisation has been performed */
|
|
if (!g_initialised) {
|
|
lp_ticker_init();
|
|
}
|
|
|
|
/* What mbed expects here is a 32 bit timer value. There is no
|
|
* way to reset the RTC so, to pretend it is 32 bits, we have to
|
|
* maintain a 32-bit window on it using the remembered overflow
|
|
* value */
|
|
rtcNow = get_rtc_value();
|
|
|
|
/* Put interrupts back */
|
|
core_util_critical_section_exit();
|
|
|
|
return ticksToUSeconds(rtcNow - g_last_32bit_overflow_value);
|
|
}
|
|
|
|
void lp_ticker_set_interrupt(timestamp_t time)
|
|
{
|
|
uint32_t timeNow = get_rtc_value() - g_last_32bit_overflow_value;
|
|
uint32_t timeOffset = uSecondsToTicks(time) - timeNow;
|
|
|
|
/* Disable interrupts to avoid collisions */
|
|
core_util_critical_section_enter();
|
|
|
|
g_user_interrupt_pending = false;
|
|
g_user_interrupt_set = false;
|
|
|
|
/* Handle time slipping into the past */
|
|
if (timeOffset > 0xEFFFFFFF) {
|
|
timeOffset = 100;
|
|
}
|
|
|
|
/* Read the current time */
|
|
g_next_compare_value = get_rtc_value();
|
|
|
|
/* Add the offset */
|
|
INCREMENT_MOD(g_next_compare_value, timeOffset);
|
|
|
|
/* We must let the normal overflow interrupt occur as
|
|
* well as setting this interrupt so, if the value
|
|
* of 'time' would occur after the overflow point,
|
|
* put the change of compare-value off until afterwards. */
|
|
/* TODO: this needs proper testing. */
|
|
if (g_next_32bit_overflow_value > g_next_compare_value) {
|
|
/* The easy case, no overlap */
|
|
} else {
|
|
/* Could be because g_next_compare_value has wrapped (around the
|
|
* 48-bit limit of the RTC) */
|
|
if (g_next_32bit_overflow_value - g_next_compare_value >= MODULO_32BIT) {
|
|
/* The wrap case, we're OK */
|
|
} else {
|
|
/* There is an overlap, apply the value later */
|
|
g_user_interrupt_pending = true;
|
|
|
|
if (g_next_32bit_overflow_value == g_next_compare_value) {
|
|
/* If they are on top of each other, bump this
|
|
* one forward to avoid losing the interrupt */
|
|
INCREMENT_MOD(g_next_compare_value, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!g_user_interrupt_pending) {
|
|
/* Make the change immediately */
|
|
set_interrupt_to_user_value();
|
|
}
|
|
|
|
/* Put interrupts back */
|
|
core_util_critical_section_exit();
|
|
}
|
|
|
|
void lp_ticker_fire_interrupt(void)
|
|
{
|
|
// user interrupt only set, this will invoke from ISR routine directly lp handler
|
|
g_user_interrupt_pending = false;
|
|
g_user_interrupt_set = true;
|
|
NVIC_SetPendingIRQ(RTC_IRQn);
|
|
}
|
|
|
|
void lp_ticker_disable_interrupt(void)
|
|
{
|
|
/* Can't disable interrupts as we need them to manage
|
|
* overflow. Instead, switch off the user part. */
|
|
g_user_interrupt_pending = false;
|
|
g_user_interrupt_set = false;
|
|
}
|
|
|
|
void lp_ticker_clear_interrupt(void)
|
|
{
|
|
/* Can't disable interrupts as we need them to manage
|
|
* overflow. Instead, switch off the user part. */
|
|
g_user_interrupt_pending = false;
|
|
g_user_interrupt_set = false;
|
|
}
|