blob: 4e4910e68582993f93f9869374254807ebf45fae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#include <assert.h>
#include "os/mynewt.h"
#include "hal/hal_os_tick.h"
#include "nrf.h"
#include "mcu/cmsis_nvic.h"
/* The OS scheduler requires a low-frequency timer. */
#if MYNEWT_VAL(OS_SCHEDULING) && \
!MYNEWT_VAL(XTAL_32768) && \
!MYNEWT_VAL(XTAL_RC) && \
!MYNEWT_VAL(XTAL_32768_SYNTH)
#error The OS scheduler requires a low-frequency timer; enable one of: XTAL_32768, XTAL_RC, or XTAL_32768_SYNTH
#endif
#define RTC_FREQ 32768 /* in Hz */
#define OS_TICK_TIMER NRF_RTC1
#define OS_TICK_IRQ RTC1_IRQn
#define OS_TICK_CMPREG 3 /* generate timer interrupt */
#define RTC_COMPARE_INT_MASK(ccreg) (1UL << ((ccreg) + 16))
struct hal_os_tick
{
int ticks_per_ostick;
os_time_t max_idle_ticks;
uint32_t lastocmp;
};
struct hal_os_tick g_hal_os_tick;
/*
* Implement (x - y) where the range of both 'x' and 'y' is limited to 24-bits.
*
* For example:
*
* sub24(0, 0xffffff) = 1
* sub24(0xffffff, 0xfffffe) = 1
* sub24(0xffffff, 0) = -1
* sub24(0x7fffff, 0) = 8388607
* sub24(0x800000, 0) = -8388608
*/
static inline int
sub24(uint32_t x, uint32_t y)
{
int result;
assert(x <= 0xffffff);
assert(y <= 0xffffff);
result = x - y;
if (result & 0x800000) {
return (result | 0xff800000);
} else {
return (result & 0x007fffff);
}
}
static inline uint32_t
nrf52_os_tick_counter(void)
{
return OS_TICK_TIMER->COUNTER;
}
static inline void
nrf52_os_tick_set_ocmp(uint32_t ocmp)
{
int delta;
uint32_t counter;
OS_ASSERT_CRITICAL();
while (1) {
ocmp &= 0xffffff;
OS_TICK_TIMER->CC[OS_TICK_CMPREG] = ocmp;
counter = nrf52_os_tick_counter();
/*
* From nRF52 Product specification
*
* - If Counter is 'N' writing (N) or (N + 1) to CC register
* may not trigger a compare event.
*
* - If Counter is 'N' writing (N + 2) to CC register is guaranteed
* to trigger a compare event at 'N + 2'.
*/
delta = sub24(ocmp, counter);
if (delta > 2) {
break;
}
ocmp += g_hal_os_tick.ticks_per_ostick;
}
}
static void
nrf52_timer_handler(void)
{
int delta;
int ticks;
os_sr_t sr;
uint32_t counter;
os_trace_isr_enter();
OS_ENTER_CRITICAL(sr);
/* Calculate elapsed ticks and advance OS time. */
counter = nrf52_os_tick_counter();
delta = sub24(counter, g_hal_os_tick.lastocmp);
ticks = delta / g_hal_os_tick.ticks_per_ostick;
os_time_advance(ticks);
/* Clear timer interrupt */
OS_TICK_TIMER->EVENTS_COMPARE[OS_TICK_CMPREG] = 0;
/* Update the time associated with the most recent tick */
g_hal_os_tick.lastocmp = (g_hal_os_tick.lastocmp +
(ticks * g_hal_os_tick.ticks_per_ostick)) & 0xffffff;
/* Update the output compare to interrupt at the next tick */
nrf52_os_tick_set_ocmp(g_hal_os_tick.lastocmp + g_hal_os_tick.ticks_per_ostick);
OS_EXIT_CRITICAL(sr);
os_trace_isr_exit();
}
void
os_tick_idle(os_time_t ticks)
{
uint32_t ocmp;
OS_ASSERT_CRITICAL();
if (ticks > 0) {
/*
* Enter tickless regime during long idle durations.
*/
if (ticks > g_hal_os_tick.max_idle_ticks) {
ticks = g_hal_os_tick.max_idle_ticks;
}
ocmp = g_hal_os_tick.lastocmp + (ticks*g_hal_os_tick.ticks_per_ostick);
nrf52_os_tick_set_ocmp(ocmp);
}
__DSB();
__WFI();
if (ticks > 0) {
/*
* Update OS time before anything else when coming out of
* the tickless regime.
*/
nrf52_timer_handler();
}
}
void
os_tick_init(uint32_t os_ticks_per_sec, int prio)
{
uint32_t sr;
assert(RTC_FREQ % os_ticks_per_sec == 0);
g_hal_os_tick.lastocmp = 0;
g_hal_os_tick.ticks_per_ostick = RTC_FREQ / os_ticks_per_sec;
/*
* The maximum number of OS ticks allowed to elapse during idle is
* limited to 1/4th the number of timer ticks before the 24-bit counter
* rolls over.
*/
g_hal_os_tick.max_idle_ticks = (1UL << 22) / g_hal_os_tick.ticks_per_ostick;
/* disable interrupts */
OS_ENTER_CRITICAL(sr);
/* Set isr in vector table and enable interrupt */
NVIC_SetPriority(OS_TICK_IRQ, prio);
NVIC_SetVector(OS_TICK_IRQ, (uint32_t)nrf52_timer_handler);
NVIC_EnableIRQ(OS_TICK_IRQ);
/*
* Program the OS_TICK_TIMER to operate at 32KHz and trigger an output
* compare interrupt at a rate of 'os_ticks_per_sec'.
*/
OS_TICK_TIMER->TASKS_STOP = 1;
OS_TICK_TIMER->TASKS_CLEAR = 1;
OS_TICK_TIMER->EVTENCLR = 0xffffffff;
OS_TICK_TIMER->INTENCLR = 0xffffffff;
OS_TICK_TIMER->INTENSET = RTC_COMPARE_INT_MASK(OS_TICK_CMPREG);
OS_TICK_TIMER->EVENTS_COMPARE[OS_TICK_CMPREG] = 0;
OS_TICK_TIMER->CC[OS_TICK_CMPREG] = g_hal_os_tick.ticks_per_ostick;
OS_TICK_TIMER->TASKS_START = 1;
OS_EXIT_CRITICAL(sr);
}