blob: 3594bc6ed7992730905258ca70e9ea20ed2f3d42 [file] [log] [blame]
/* Copyright (c) 2018, Nordic Semiconductor ASA
* 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 Nordic Semiconductor ASA 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 Timer Coordinator module.
*
*/
#include "nrf_802154_timer_coord.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "nrf_802154_config.h"
#include "nrf_802154_debug.h"
#include "nrf_ppi.h"
#include "platform/hp_timer/nrf_802154_hp_timer.h"
#include "platform/lp_timer/nrf_802154_lp_timer.h"
#define DIV_ROUND_POSITIVE(n, d) (((n) + (d) / 2) / (d))
#define DIV_ROUND_NEGATIVE(n, d) (((n) - (d) / 2) / (d))
#define DIV_ROUND(n, d) ((((n) < 0) ^ ((d) < 0)) ? \
DIV_ROUND_NEGATIVE(n, d) : \
DIV_ROUND_POSITIVE(n, d))
#define TIME_BASE (1UL << 22) ///< Unit used to calculate PPTB (Point per Time Base). It is not equal million to speed up computations and increase precision.
#define FIRST_RESYNC_TIME TIME_BASE ///< Delay of the first resynchronization. The first resynchronization is needed to measure timers drift.
#define RESYNC_TIME (4 * TIME_BASE) ///< Delay of following resynchronizations.
#define EWMA_COEF (8) ///< Weight used in the EWMA algorithm.
#define PPI_CH0 NRF_PPI_CHANNEL13
#define PPI_CH1 NRF_PPI_CHANNEL14
#define PPI_CHGRP0 NRF_PPI_CHANNEL_GROUP1
#define PPI_SYNC PPI_CH0
#define PPI_TIMESTAMP PPI_CH1
#define PPI_TIMESTAMP_GROUP PPI_CHGRP0
#if NRF_802154_FRAME_TIMESTAMP_ENABLED
// Structure holding common timepoint from both timers.
typedef struct
{
uint32_t lp_timer_time; ///< LP Timer time of common timepoint.
uint32_t hp_timer_time; ///< HP Timer time of common timepoint.
} common_timepoint_t;
// Static variables.
static common_timepoint_t m_last_sync; ///< Common timepoint of last synchronization event.
static volatile bool m_synchronized; ///< If timers were synchronized since last start.
static bool m_drift_known; ///< If timer drift value is known.
static int32_t m_drift; ///< Drift of the HP timer relatively to the LP timer [PPTB].
void nrf_802154_timer_coord_init(void)
{
uint32_t sync_event;
uint32_t sync_task;
m_drift = 0;
m_drift_known = 0;
nrf_802154_hp_timer_init();
sync_event = nrf_802154_lp_timer_sync_event_get();
sync_task = nrf_802154_hp_timer_sync_task_get();
nrf_ppi_channel_endpoint_setup(PPI_SYNC, sync_event, sync_task);
nrf_ppi_channel_enable(PPI_SYNC);
nrf_ppi_channel_include_in_group(PPI_TIMESTAMP, PPI_TIMESTAMP_GROUP);
}
void nrf_802154_timer_coord_uninit(void)
{
nrf_802154_hp_timer_deinit();
nrf_ppi_channel_disable(PPI_SYNC);
nrf_ppi_channel_endpoint_setup(PPI_SYNC, 0, 0);
nrf_ppi_group_disable(PPI_TIMESTAMP_GROUP);
nrf_ppi_channel_and_fork_endpoint_setup(PPI_TIMESTAMP, 0, 0, 0);
}
void nrf_802154_timer_coord_start(void)
{
nrf_802154_log(EVENT_TRACE_ENTER, FUNCTION_TCOOR_START);
m_synchronized = false;
nrf_802154_hp_timer_start();
nrf_802154_hp_timer_sync_prepare();
nrf_802154_lp_timer_sync_start_now();
nrf_802154_log(EVENT_TRACE_EXIT, FUNCTION_TCOOR_START);
}
void nrf_802154_timer_coord_stop(void)
{
nrf_802154_log(EVENT_TRACE_ENTER, FUNCTION_TCOOR_STOP);
nrf_802154_hp_timer_stop();
nrf_802154_lp_timer_sync_stop();
nrf_802154_log(EVENT_TRACE_EXIT, FUNCTION_TCOOR_STOP);
}
void nrf_802154_timer_coord_timestamp_prepare(uint32_t event_addr)
{
nrf_802154_log(EVENT_TRACE_ENTER, FUNCTION_TCOOR_TIMESTAMP_PREPARE);
nrf_ppi_channel_and_fork_endpoint_setup(PPI_TIMESTAMP,
event_addr,
nrf_802154_hp_timer_timestamp_task_get(),
(uint32_t)nrf_ppi_task_group_disable_address_get(
PPI_TIMESTAMP_GROUP));
nrf_ppi_group_enable(PPI_TIMESTAMP_GROUP);
nrf_802154_log(EVENT_TRACE_EXIT, FUNCTION_TCOOR_TIMESTAMP_PREPARE);
}
bool nrf_802154_timer_coord_timestamp_get(uint32_t * p_timestamp)
{
uint32_t hp_timestamp;
uint32_t hp_delta;
int32_t drift;
bool result = false;
nrf_802154_log(EVENT_TRACE_ENTER, FUNCTION_TCOOR_TIMESTAMP_GET);
assert(p_timestamp != NULL);
if (m_synchronized)
{
hp_timestamp = nrf_802154_hp_timer_timestamp_get();
hp_delta = hp_timestamp - m_last_sync.hp_timer_time;
drift = m_drift_known ?
(DIV_ROUND(((int64_t)m_drift * hp_delta), ((int64_t)TIME_BASE + m_drift))) :
0;
*p_timestamp = m_last_sync.lp_timer_time + hp_delta - drift;
result = true;
}
nrf_802154_log(EVENT_TRACE_EXIT, FUNCTION_TCOOR_TIMESTAMP_GET);
return result;
}
void nrf_802154_lp_timer_synchronized(void)
{
common_timepoint_t sync_time;
uint32_t lp_delta;
uint32_t hp_delta;
int32_t timers_diff;
int32_t drift;
int32_t tb_fraction_of_lp_delta;
nrf_802154_log(EVENT_TRACE_ENTER, FUNCTION_TCOOR_SYNCHRONIZED);
if (nrf_802154_hp_timer_sync_time_get(&sync_time.hp_timer_time))
{
sync_time.lp_timer_time = nrf_802154_lp_timer_sync_time_get();
// Calculate timers drift
if (m_synchronized)
{
lp_delta = sync_time.lp_timer_time - m_last_sync.lp_timer_time;
hp_delta = sync_time.hp_timer_time - m_last_sync.hp_timer_time;
tb_fraction_of_lp_delta = DIV_ROUND_POSITIVE(lp_delta, TIME_BASE);
timers_diff = hp_delta - lp_delta;
drift = DIV_ROUND(timers_diff, tb_fraction_of_lp_delta); // Drift in PPTB
if (m_drift_known)
{
m_drift = DIV_ROUND((m_drift * (EWMA_COEF - 1) + drift), EWMA_COEF);
}
else
{
m_drift = drift;
}
m_drift_known = true;
}
/* To avoid possible race when nrf_802154_timer_coord_timestamp_get
* is called when m_last_sync is being assigned report that we are not synchronized
* during assignment.
* This is naive solution that can be improved if needed with double buffering.
*/
m_synchronized = false;
__DMB();
m_last_sync = sync_time;
__DMB();
m_synchronized = true;
nrf_802154_hp_timer_sync_prepare();
nrf_802154_lp_timer_sync_start_at(m_last_sync.lp_timer_time,
m_drift_known ? RESYNC_TIME : FIRST_RESYNC_TIME);
}
else
{
nrf_802154_hp_timer_sync_prepare();
nrf_802154_lp_timer_sync_start_now();
}
nrf_802154_log(EVENT_TRACE_EXIT, FUNCTION_TCOOR_SYNCHRONIZED);
}
#else // NRF_802154_FRAME_TIMESTAMP_ENABLED
void nrf_802154_timer_coord_init(void)
{
// Intentionally empty
}
void nrf_802154_timer_coord_uninit(void)
{
// Intentionally empty
}
void nrf_802154_timer_coord_start(void)
{
// Intentionally empty
}
void nrf_802154_timer_coord_stop(void)
{
// Intentionally empty
}
void nrf_802154_timer_coord_timestamp_prepare(uint32_t event_addr)
{
(void)event_addr;
// Intentionally empty
}
bool nrf_802154_timer_coord_timestamp_get(uint32_t * p_timestamp)
{
(void)p_timestamp;
// Intentionally empty
return false;
}
#endif // NRF_802154_FRAME_TIMESTAMP_ENABLED