forked

targets/TARGET_ublox/TARGET_HI2110/lp_ticker.c

Committer:
Kojto
Date:
2017-08-03
Revision:
170:19eb464bc2be
Parent:
160:d5399cc887bb

File content as of revision 170:19eb464bc2be:

/* 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_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;
}