mbed-os/targets/TARGET_ublox/TARGET_HI2110/us_ticker.c

254 lines
8.2 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 usecond ticker is mapped to TIMER0. A few issues must be dealt
* with in this driver:
*
* 1. The us_ticker API must count upwards, not down.
* 2. The expected range/resolution is 32 bits each of 1 usecond,
* whereas TIMER0 runs at 48 MHz (not 1 MHz) and so actually
* has a range/resolution of 26 bits at 0.02 useconds. Software
* has to compensate for this.
*/
#include "us_ticker_api.h"
#include "mbed_critical.h"
/* ----------------------------------------------------------------
* MACROS
* ----------------------------------------------------------------*/
/* TIMER0 clock is 48 MHz */
#define CLOCK_TICKS_PER_US 48
/* The number of clock ticks in a full-run of
* TIMER0, scaled to represent useconds */
#define USECONDS_PER_FULL_TIMER0_RUN 89478485
/* ----------------------------------------------------------------
* TYPES
* ----------------------------------------------------------------*/
/* ----------------------------------------------------------------
* GLOBAL VARIABLES
* ----------------------------------------------------------------*/
/* Are we ready? */
static bool g_initialised = false;
/* Keep track of the number of useconds elapsed. */
static uint32_t g_us_overflow = 0;
/* The number of useconds to increment the by at each interrupt */
static uint32_t g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
/* Keep track of extra loops required to represent a particular time
* as the HW timer runs faster than 1 MHz */
static uint32_t g_timer_extra_loops_required = 0;
static uint32_t g_timer_extra_loops_done = 0;
/* Keep track of any adjustment due to user interrupts . */
static uint32_t g_user_interrupt_offset = 0;
/* Flag that a user timer is running */
static bool g_user_interrupt = false;
/* ----------------------------------------------------------------
* FUNCTION PROTOTYPES
* ----------------------------------------------------------------*/
static inline uint32_t divide_by_48(uint32_t x);
/* ----------------------------------------------------------------
* NON-API FUNCTIONS
* ----------------------------------------------------------------*/
/* Perform a divide-by-48 operation.
* This is done as a multiply-shift operation to take advantage of
* the ARM 32 bit single-cycle multiply and avoid using division;
* 1/48 is equivalent to 1365/2^16. It is also done in two halves
* to make sure that the multiplies fit into 32 bits.
*
* The principle is:
* - divide the top 16 bits by 48 using multiply-shift (=> x1),
* - work out the remainder of that operation and divide that by 48 (=> x1r),
* - divide the bottom 16 bits by 48 using multiply-shift (=> x2),
* - add the lot together to get the result.
*
* The cost is 29 instructions.
*/
static inline uint32_t divide_by_48(uint32_t x)
{
uint32_t x1 = ((x >> 16) * 1365) >> 16;
uint32_t x1r = ((x & 0xFFFF0000) - ((x1 * 48) << 16));
x1r = (x1r * 1365) >> 16;
uint32_t x2 = ((x & 0xFFFF) * 1365) >> 16;
return (x1 << 16) + x1r + x2;
}
/* Timer0 handler */
void IRQ1_TMR0_Handler(void)
{
if (g_initialised) {
/* Increment the overflow count and set the increment
* value for next time */
g_us_overflow += g_us_overflow_increment;
g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
/* Now handle the user interrupt case */
if (g_user_interrupt) {
if (g_timer_extra_loops_done < g_timer_extra_loops_required) {
/* Let the timer go round again */
g_timer_extra_loops_done++;
} else {
/* We've done with looping around for a user interrupt */
g_user_interrupt = false;
/* Call the mbed API */
us_ticker_irq_handler();
}
}
}
NVIC_ClearPendingIRQ(Timer_IRQn);
}
/* ----------------------------------------------------------------
* MBED API CALLS
* ----------------------------------------------------------------*/
void us_ticker_init(void)
{
if (!g_initialised) {
/* Reset the globals */
g_timer_extra_loops_done = 0;
g_timer_extra_loops_required = 0;
g_us_overflow = 0;
g_us_overflow_increment = USECONDS_PER_FULL_TIMER0_RUN;
g_user_interrupt_offset = 0;
g_user_interrupt = false;
/* Get the timer running (starting at what is zero,
* once inverted), with repeat */
NVIC_ClearPendingIRQ(Timer_IRQn);
TIMER0_LOAD = 0xFFFFFFFF;
TIMER0_CTRL = 0x03;
NVIC_EnableIRQ(Timer_IRQn);
g_initialised = true;
}
}
uint32_t us_ticker_read()
{
uint32_t timeValue;
/* This can be called before initialisation has been performed */
if (!g_initialised) {
us_ticker_init();
}
/* Disable interrupts to avoid collisions */
core_util_critical_section_enter();
/* Get the timer value, adding the offset in case we've been moved
* around by user activity, inverting it (as a count-up timer is
* expected), then scaling it to useconds and finally adding the
* usecond overflow value to make up the 32-bit usecond total */
timeValue = divide_by_48(~(TIMER0_TIME + g_user_interrupt_offset)) + g_us_overflow;
/* Put interrupts back */
core_util_critical_section_exit();
return timeValue;
}
/* NOTE: it seems to be an accepted fact that users
* will never ask for a timeout of more than 2^31 useconds
* and hence it's possible to do signed arithmetic
*/
void us_ticker_set_interrupt(timestamp_t timestamp)
{
g_timer_extra_loops_required = 0;
g_timer_extra_loops_done = 0;
int32_t timeDelta;
/* Disable interrupts to avoid collisions */
core_util_critical_section_enter();
/* Establish how far we're being asked to move */
timeDelta = (int32_t) ((uint32_t) timestamp - us_ticker_read());
if (timeDelta <= 0) {
/* Make delta positive if it's not, it will expire pretty quickly */
/* Note: can't just call us_ticker_irq_handler() directly as we
* may already be in it and will overflow the stack */
timeDelta = 1;
}
/* The TIMER0 clock source is greater than 1 MHz, so
* work out how many times we have to go around
* and what the remainder is */
g_timer_extra_loops_required = (uint32_t) timeDelta / USECONDS_PER_FULL_TIMER0_RUN;
timeDelta -= g_timer_extra_loops_required * USECONDS_PER_FULL_TIMER0_RUN;
/* Next time we hit the interrupt the increment will be smaller */
g_us_overflow_increment = (uint32_t) timeDelta;
/* We're about to modify the timer value; work out the
* difference so that we can compensate for it when
* the time is read */
timeDelta = timeDelta * CLOCK_TICKS_PER_US;
g_user_interrupt_offset += TIMER0_TIME - timeDelta;
/* Run for the remainder first, then we can loop for the full
* USECONDS_PER_FULL_TIMER0_RUN afterwards */
TIMER0_LOAD = timeDelta;
/* A user interrupt is now running */
g_user_interrupt = true;
/* Put interrupts back */
core_util_critical_section_exit();
}
void us_ticker_fire_interrupt(void)
{
g_user_interrupt = true;
NVIC_SetPendingIRQ(Timer_IRQn);
}
void us_ticker_disable_interrupt(void)
{
/* Can't actually disable the interrupt here
* as we need it to manage the timer overflow,
* instead switch off the user interrupt part */
g_user_interrupt = false;
g_timer_extra_loops_required = 0;
g_us_overflow_increment = 0;
}
void us_ticker_clear_interrupt(void)
{
/* As above, can't clear the interrupt as it
* may just be an overflow interrupt, instead
* clear the variables */
g_user_interrupt = false;
g_timer_extra_loops_required = 0;
g_us_overflow_increment = 0;
}