mbed library sources. Supersedes mbed-src.
Fork of mbed-dev by
Diff: targets/TARGET_ublox/TARGET_HI2110/lp_ticker.c
- Revision:
- 150:02e0a0aed4ec
- Child:
- 160:d5399cc887bb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/TARGET_ublox/TARGET_HI2110/lp_ticker.c Tue Nov 08 17:45:16 2016 +0000 @@ -0,0 +1,340 @@ +/* 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 "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_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; +}