blob: 5f033a7fbb8fd17432e280004aeabfc767da1be2 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the OpenThread platform abstraction for the alarm.
*
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <openthread/platform/alarm.h>
#include <openthread/platform/diag.h>
#include <openthread/platform/usec-alarm.h>
#include "platform-config.h"
#include "cmsis/core_cmFunc.h"
#include <drivers/clock/nrf_drv_clock.h>
#include <hal/nrf_rtc.h>
#include <openthread/config.h>
#include <openthread/types.h>
#define RTC_FREQUENCY 32768ULL
#define CEIL_DIV(A, B) (((A) + (B) - 1ULL) / (B))
#define US_PER_MS 1000ULL
#define US_PER_S 1000000ULL
#define US_PER_TICK CEIL_DIV(US_PER_S, RTC_FREQUENCY)
#define MS_PER_S 1000UL
#define MS_PER_OVERFLOW (512UL * MS_PER_S) ///< Time that has passed between overflow events. On full RTC speed, it occurs every 512 s.
typedef enum
{
kMsTimer,
kUsTimer,
kNumTimers
} AlarmIndex;
typedef struct
{
volatile bool mFireAlarm; ///< Information for processing function, that alarm should fire.
uint32_t mTargetTime; ///< Alarm fire time (in millisecond for MsTimer, in microsecond for UsTimer)
} AlarmData;
typedef struct
{
uint32_t mChannelNumber;
uint32_t mCompareEventMask;
nrf_rtc_event_t mCompareEvent;
nrf_rtc_int_t mCompareInt;
} AlarmChannelData;
static volatile uint32_t sTimeOffset = 0; ///< Time offset to keep track of current time (in millisecond).
static AlarmData sTimerData[kNumTimers]; ///< Data of the timers.
static const AlarmChannelData sChannelData[kNumTimers] =
{
[kMsTimer] = {
.mChannelNumber = 0,
.mCompareEventMask = RTC_EVTEN_COMPARE0_Msk,
.mCompareEvent = NRF_RTC_EVENT_COMPARE_0,
.mCompareInt = NRF_RTC_INT_COMPARE0_MASK,
},
[kUsTimer] = {
.mChannelNumber = 1,
.mCompareEventMask = RTC_EVTEN_COMPARE1_Msk,
.mCompareEvent = NRF_RTC_EVENT_COMPARE_1,
.mCompareInt = NRF_RTC_INT_COMPARE1_MASK,
}
};
static otPlatUsecAlarmHandler sUsecHandler = NULL; ///< Handler called when usec alarm fires.
static void *sUsecContext = NULL; ///< The context information passed to the usec handler callback.
static void HandleOverflow(void);
static inline uint32_t TimeToTicks(uint32_t aTime, AlarmIndex aIndex)
{
uint32_t ticks;
if (aIndex == kMsTimer)
{
ticks = (uint32_t)CEIL_DIV((aTime * US_PER_MS * RTC_FREQUENCY), US_PER_S) & RTC_CC_COMPARE_Msk;
}
else
{
ticks = (uint32_t)CEIL_DIV((aTime * RTC_FREQUENCY), US_PER_S) & RTC_CC_COMPARE_Msk;
}
return ticks;
}
static inline uint64_t TicksToTime(uint32_t aTicks)
{
return CEIL_DIV((US_PER_S * (uint64_t)aTicks), RTC_FREQUENCY);
}
static inline uint64_t AlarmGetCurrentTime()
{
uint32_t rtcValue1;
uint32_t rtcValue2;
uint32_t offset;
rtcValue1 = nrf_rtc_counter_get(RTC_INSTANCE);
__DMB();
offset = sTimeOffset;
__DMB();
rtcValue2 = nrf_rtc_counter_get(RTC_INSTANCE);
if ((rtcValue2 < rtcValue1) || (rtcValue1 == 0))
{
// Overflow detected. Additional condition (rtcValue1 == 0) covers situation when overflow occurred in
// interrupt state, before this function was entered. But in general, this function shall not be called
// from interrupt other than alarm interrupt.
// Wait at least 20 cycles, to ensure that if interrupt is going to be called, it will be called now.
for (uint32_t i = 0; i < 4; i++)
{
__NOP();
__NOP();
__NOP();
}
// If the event flag is still on, it means that the interrupt was not called, as we are in interrupt state.
if (nrf_rtc_event_pending(RTC_INSTANCE, NRF_RTC_EVENT_OVERFLOW))
{
HandleOverflow();
}
offset = sTimeOffset;
}
return US_PER_MS * (uint64_t)offset + TicksToTime(rtcValue2);
}
static inline uint32_t AlarmGetCurrentTimeRtcProtected(AlarmIndex aIndex)
{
uint64_t usecTime = AlarmGetCurrentTime() + 2 * US_PER_TICK;
uint32_t currentTime;
if (aIndex == kMsTimer)
{
currentTime = (uint32_t)(usecTime / US_PER_MS);
}
else
{
currentTime = (uint32_t)usecTime;
}
return currentTime;
}
static inline bool AlarmShallStrike(uint32_t aNow, AlarmIndex aIndex)
{
uint32_t diff = aNow - sTimerData[aIndex].mTargetTime;
// Three cases:
// 1) aNow is before mTargetTime => Difference is negative (last bit of difference is set) => Returning false.
// 2) aNow is same as mTargetTime => Difference is zero (last bit of difference is clear) => Returning true.
// 3) aNow is after mTargetTime => Difference is positive (last bit of difference is clear) => Returning true.
return !((diff & (1UL << 31)) != 0);
}
static void HandleCompareMatch(AlarmIndex aIndex, bool aSkipCheck)
{
nrf_rtc_event_clear(RTC_INSTANCE, sChannelData[aIndex].mCompareEvent);
uint64_t usecTime = AlarmGetCurrentTime();
uint32_t now;
if (aIndex == kMsTimer)
{
now = (uint32_t)(usecTime / US_PER_MS);
}
else
{
now = (uint32_t)usecTime;
}
// In case the target time was larger than single overflow,
// we should only strike the timer on final compare event.
if (aSkipCheck || AlarmShallStrike(now, aIndex))
{
nrf_rtc_event_disable(RTC_INSTANCE, sChannelData[aIndex].mCompareEventMask);
nrf_rtc_int_disable(RTC_INSTANCE, sChannelData[aIndex].mCompareInt);
sTimerData[aIndex].mFireAlarm = true;
}
}
static void HandleOverflow(void)
{
nrf_rtc_event_clear(RTC_INSTANCE, NRF_RTC_EVENT_OVERFLOW);
// Increment counter on overflow.
sTimeOffset = sTimeOffset + MS_PER_OVERFLOW;
}
static void AlarmStartAt(uint32_t aTargetTime, AlarmIndex aIndex)
{
uint32_t targetCounter;
uint32_t now;
nrf_rtc_int_disable(RTC_INSTANCE, sChannelData[aIndex].mCompareInt);
nrf_rtc_event_enable(RTC_INSTANCE, sChannelData[aIndex].mCompareEventMask);
sTimerData[aIndex].mTargetTime = aTargetTime;
targetCounter = TimeToTicks(sTimerData[aIndex].mTargetTime, aIndex);
nrf_rtc_cc_set(RTC_INSTANCE, sChannelData[aIndex].mChannelNumber, targetCounter);
now = AlarmGetCurrentTimeRtcProtected(aIndex);
if (AlarmShallStrike(now, aIndex))
{
HandleCompareMatch(aIndex, true);
}
else
{
nrf_rtc_int_enable(RTC_INSTANCE, sChannelData[aIndex].mCompareInt);
}
}
static void AlarmStop(AlarmIndex aIndex)
{
nrf_rtc_event_disable(RTC_INSTANCE, sChannelData[aIndex].mCompareEventMask);
nrf_rtc_int_disable(RTC_INSTANCE, sChannelData[aIndex].mCompareInt);
nrf_rtc_event_clear(RTC_INSTANCE, sChannelData[aIndex].mCompareEvent);
sTimerData[aIndex].mFireAlarm = false;
}
void nrf5AlarmInit(void)
{
sTimeOffset = 0;
memset(sTimerData, 0, sizeof(sTimerData));
// Setup low frequency clock.
nrf_drv_clock_lfclk_request(NULL);
while (!nrf_drv_clock_lfclk_is_running()) {}
// Setup RTC timer.
NVIC_SetPriority(RTC_IRQN, RTC_IRQ_PRIORITY);
NVIC_ClearPendingIRQ(RTC_IRQN);
NVIC_EnableIRQ(RTC_IRQN);
nrf_rtc_prescaler_set(RTC_INSTANCE, 0);
nrf_rtc_event_clear(RTC_INSTANCE, NRF_RTC_EVENT_OVERFLOW);
nrf_rtc_event_enable(RTC_INSTANCE, RTC_EVTEN_OVRFLW_Msk);
nrf_rtc_int_enable(RTC_INSTANCE, NRF_RTC_INT_OVERFLOW_MASK);
for (uint32_t i = 0; i < kNumTimers; i++)
{
nrf_rtc_event_clear(RTC_INSTANCE, sChannelData[i].mCompareEvent);
nrf_rtc_event_disable(RTC_INSTANCE, sChannelData[i].mCompareEventMask);
nrf_rtc_int_disable(RTC_INSTANCE, sChannelData[i].mCompareInt);
}
nrf_rtc_task_trigger(RTC_INSTANCE, NRF_RTC_TASK_START);
}
void nrf5AlarmDeinit(void)
{
nrf_rtc_task_trigger(RTC_INSTANCE, NRF_RTC_TASK_STOP);
for (uint32_t i = 0; i < kNumTimers; i++)
{
nrf_rtc_event_clear(RTC_INSTANCE, sChannelData[i].mCompareEvent);
nrf_rtc_event_disable(RTC_INSTANCE, sChannelData[i].mCompareEventMask);
nrf_rtc_int_disable(RTC_INSTANCE, sChannelData[i].mCompareInt);
}
nrf_rtc_int_disable(RTC_INSTANCE, NRF_RTC_INT_OVERFLOW_MASK);
nrf_rtc_event_disable(RTC_INSTANCE, RTC_EVTEN_OVRFLW_Msk);
nrf_rtc_event_clear(RTC_INSTANCE, NRF_RTC_EVENT_OVERFLOW);
NVIC_DisableIRQ(RTC_IRQN);
NVIC_ClearPendingIRQ(RTC_IRQN);
NVIC_SetPriority(RTC_IRQN, 0);
nrf_drv_clock_lfclk_release();
}
void nrf5AlarmProcess(otInstance *aInstance)
{
if (sTimerData[kMsTimer].mFireAlarm)
{
sTimerData[kMsTimer].mFireAlarm = false;
#if OPENTHREAD_ENABLE_DIAG
if (otPlatDiagModeGet())
{
otPlatDiagAlarmFired(aInstance);
}
else
#endif
{
otPlatAlarmFired(aInstance);
}
}
if (sTimerData[kUsTimer].mFireAlarm)
{
sTimerData[kUsTimer].mFireAlarm = false;
sUsecHandler(sUsecContext);
}
}
uint32_t otPlatAlarmGetNow(void)
{
return (uint32_t)(AlarmGetCurrentTime() / US_PER_MS);
}
void otPlatAlarmStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt)
{
(void)aInstance;
uint32_t targetTime = aT0 + aDt;
AlarmStartAt(targetTime, kMsTimer);
}
void otPlatAlarmStop(otInstance *aInstance)
{
(void)aInstance;
AlarmStop(kMsTimer);
}
uint32_t otPlatUsecAlarmGetNow(void)
{
return (uint32_t)AlarmGetCurrentTime();
}
void otPlatUsecAlarmStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt,
otPlatUsecAlarmHandler aHandler, void *aContext)
{
(void)aInstance;
uint32_t targetTime = aT0 + aDt;
AlarmStartAt(targetTime, kUsTimer);
sUsecHandler = aHandler;
sUsecContext = aContext;
}
void otPlatUsecAlarmStop(otInstance *aInstance)
{
(void)aInstance;
AlarmStop(kUsTimer);
}
void RTC_IRQ_HANDLER(void)
{
// Handle overflow.
if (nrf_rtc_event_pending(RTC_INSTANCE, NRF_RTC_EVENT_OVERFLOW))
{
HandleOverflow();
}
// Handle compare match.
for (uint32_t i = 0; i < kNumTimers; i++)
{
if (nrf_rtc_int_is_enabled(RTC_INSTANCE, sChannelData[i].mCompareInt) &&
nrf_rtc_event_pending(RTC_INSTANCE, sChannelData[i].mCompareEvent))
{
HandleCompareMatch((AlarmIndex)i, false);
}
}
}