282 lines
7.9 KiB
C
282 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2021 Nordic Semiconductor ASA.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <nrfx_dppi.h>
|
|
#include <hal/nrf_ipc.h>
|
|
#include <helpers/nrfx_gppi.h>
|
|
#include <zephyr/drivers/timer/nrf_rtc_timer.h>
|
|
#include <zephyr/drivers/mbox.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/logging/log_ctrl.h>
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(sync_rtc, CONFIG_SYNC_RTC_LOG_LEVEL);
|
|
|
|
/* Arbitrary delay is used needed to handle cases when offset between cores is
|
|
* small and rtc synchronization process might not handle events on time.
|
|
* Setting high value prolongs synchronization process but setting too low may
|
|
* lead synchronization failure if offset between cores is small and/or there
|
|
* are significant interrupt handling latencies.
|
|
*/
|
|
#define RTC_SYNC_ARBITRARY_DELAY 100
|
|
|
|
static uint32_t sync_cc;
|
|
static int32_t nrf53_sync_offset = -EBUSY;
|
|
|
|
union rtc_sync_channels {
|
|
uint32_t raw;
|
|
struct {
|
|
uint8_t ppi;
|
|
uint8_t rtc;
|
|
uint8_t ipc_out;
|
|
uint8_t ipc_in;
|
|
} ch;
|
|
};
|
|
|
|
/* Algorithm for establishing RTC offset on the network side.
|
|
*
|
|
* Assumptions:
|
|
* APP starts first thus its RTC is ahead. Only network will need to adjust its
|
|
* time. Because APP will capture the offset but NET needs it, algorithm
|
|
* consists of two stages: Getting offset on APP side, passing this offset to
|
|
* NET core. To keep it simple and independent from IPM protocols, value is passed
|
|
* using just IPC, PPI and RTC.
|
|
*
|
|
* 1st stage:
|
|
* APP: setup PPI connection from IPC_RECEIVE to RTC CAPTURE, enable interrupt
|
|
* IPC received.
|
|
* NET: setup RTC CC for arbitrary offset from now, setup PPI from RTC_COMPARE to IPC_SEND
|
|
* Record value set to CC.
|
|
*
|
|
* When APP will capture the value it needs to be passed to NET since it will be
|
|
* capable of calculating the offset since it know what counter value corresponds
|
|
* to the value captured on APP side.
|
|
*
|
|
* 2nd stage:
|
|
* APP: Sets Compare event for value = 2 * captured value + arbitrary offset
|
|
* NET: setup PPI from IPC_RECEIVE to RTC CAPTURE
|
|
*
|
|
* When NET RTC captures IPC event it takes CC value and knowing CC value previously
|
|
* used by NET and arbitrary offset (which is the same on APP and NET) is able
|
|
* to calculate exact offset between RTC counters.
|
|
*
|
|
* Note, arbitrary delay is used to accommodate for the case when NET-APP offset
|
|
* is small enough that interrupt latency would impact it. NET-APP offset depends
|
|
* on when NET core is reset and time when RTC system clock is initialized.
|
|
*/
|
|
|
|
/* Setup or clear connection from IPC_RECEIVE to RTC_CAPTURE
|
|
*
|
|
* @param channels Details about channels
|
|
* @param setup If true connection is setup, else it is cleared.
|
|
*/
|
|
static void ppi_ipc_to_rtc(union rtc_sync_channels channels, bool setup)
|
|
{
|
|
nrf_ipc_event_t ipc_evt = nrf_ipc_receive_event_get(channels.ch.ipc_in);
|
|
uint32_t task_addr = z_nrf_rtc_timer_capture_task_address_get(channels.ch.rtc);
|
|
|
|
if (setup) {
|
|
nrfx_gppi_task_endpoint_setup(channels.ch.ppi, task_addr);
|
|
nrf_ipc_publish_set(NRF_IPC, ipc_evt, channels.ch.ppi);
|
|
} else {
|
|
nrfx_gppi_task_endpoint_clear(channels.ch.ppi, task_addr);
|
|
nrf_ipc_publish_clear(NRF_IPC, ipc_evt);
|
|
}
|
|
}
|
|
|
|
/* Setup or clear connection from RTC_COMPARE to IPC_SEND
|
|
*
|
|
* @param channels Details about channels
|
|
* @param setup If true connection is setup, else it is cleared.
|
|
*/
|
|
static void ppi_rtc_to_ipc(union rtc_sync_channels channels, bool setup)
|
|
{
|
|
uint32_t evt_addr = z_nrf_rtc_timer_compare_evt_address_get(channels.ch.rtc);
|
|
nrf_ipc_task_t ipc_task = nrf_ipc_send_task_get(channels.ch.ipc_out);
|
|
|
|
if (setup) {
|
|
nrf_ipc_subscribe_set(NRF_IPC, ipc_task, channels.ch.ppi);
|
|
nrfx_gppi_event_endpoint_setup(channels.ch.ppi, evt_addr);
|
|
} else {
|
|
nrfx_gppi_event_endpoint_clear(channels.ch.ppi, evt_addr);
|
|
nrf_ipc_subscribe_clear(NRF_IPC, ipc_task);
|
|
}
|
|
}
|
|
|
|
/* Free DPPI and RTC channels */
|
|
static void free_resources(union rtc_sync_channels channels)
|
|
{
|
|
nrfx_err_t err;
|
|
|
|
nrfx_gppi_channels_disable(BIT(channels.ch.ppi));
|
|
|
|
z_nrf_rtc_timer_chan_free(channels.ch.rtc);
|
|
|
|
err = nrfx_dppi_channel_free(channels.ch.ppi);
|
|
__ASSERT_NO_MSG(err == NRFX_SUCCESS);
|
|
}
|
|
|
|
int z_nrf_rtc_timer_nrf53net_offset_get(void)
|
|
{
|
|
if (!IS_ENABLED(CONFIG_SOC_COMPATIBLE_NRF5340_CPUNET)) {
|
|
return -ENOSYS;
|
|
}
|
|
|
|
return nrf53_sync_offset;
|
|
}
|
|
|
|
static void rtc_cb(int32_t id, uint64_t cc_value, void *user_data)
|
|
{
|
|
ARG_UNUSED(id);
|
|
ARG_UNUSED(cc_value);
|
|
|
|
union rtc_sync_channels channels;
|
|
|
|
channels.raw = (uint32_t)user_data;
|
|
ppi_rtc_to_ipc(channels, false);
|
|
if (IS_ENABLED(CONFIG_SOC_COMPATIBLE_NRF5340_CPUAPP)) {
|
|
/* APP: Synchronized completed */
|
|
free_resources(channels);
|
|
} else {
|
|
/* Compare event generated, reconfigure PPI and wait for
|
|
* IPC event from APP.
|
|
*/
|
|
ppi_ipc_to_rtc(channels, true);
|
|
}
|
|
}
|
|
|
|
static log_timestamp_t sync_rtc_timestamp_get(void)
|
|
{
|
|
return (log_timestamp_t)(sys_clock_tick_get() + nrf53_sync_offset);
|
|
}
|
|
|
|
static void remote_callback(void *user_data)
|
|
{
|
|
extern const struct log_link *log_link_ipc_get_link(void);
|
|
|
|
union rtc_sync_channels channels;
|
|
uint32_t cc;
|
|
|
|
channels.raw = (uint32_t)user_data;
|
|
|
|
cc = z_nrf_rtc_timer_compare_read(channels.ch.rtc);
|
|
|
|
/* Clear previous task,event */
|
|
ppi_ipc_to_rtc(channels, false);
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_COMPATIBLE_NRF5340_CPUAPP)) {
|
|
/* Setup new connection from RTC to IPC and set RTC to a new
|
|
* interval that contains captured offset.
|
|
*/
|
|
ppi_rtc_to_ipc(channels, true);
|
|
|
|
z_nrf_rtc_timer_set(channels.ch.rtc, cc + cc + RTC_SYNC_ARBITRARY_DELAY,
|
|
rtc_cb, (void *)channels.raw);
|
|
} else {
|
|
/* Synchronization completed */
|
|
free_resources(channels);
|
|
nrf53_sync_offset = cc - RTC_SYNC_ARBITRARY_DELAY - 2 * sync_cc;
|
|
if (IS_ENABLED(CONFIG_NRF53_SYNC_RTC_LOG_TIMESTAMP)) {
|
|
uint32_t offset_us =
|
|
(uint64_t)nrf53_sync_offset * 1000000 /
|
|
sys_clock_hw_cycles_per_sec();
|
|
|
|
log_set_timestamp_func(sync_rtc_timestamp_get,
|
|
sys_clock_hw_cycles_per_sec());
|
|
LOG_INF("Updated timestamp to synchronized RTC by %d ticks (%dus)",
|
|
nrf53_sync_offset, offset_us);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mbox_callback(const struct device *dev, mbox_channel_id_t channel_id,
|
|
void *user_data, struct mbox_msg *data)
|
|
{
|
|
int err;
|
|
|
|
err = mbox_set_enabled(dev, channel_id, false);
|
|
|
|
(void)err;
|
|
__ASSERT_NO_MSG(err == 0);
|
|
|
|
remote_callback(user_data);
|
|
}
|
|
|
|
static int mbox_rx_init(void *user_data)
|
|
{
|
|
const struct device *dev;
|
|
int err;
|
|
|
|
dev = COND_CODE_1(CONFIG_MBOX, (DEVICE_DT_GET(DT_NODELABEL(mbox))), (NULL));
|
|
if (dev == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
err = mbox_register_callback(dev, CONFIG_NRF53_SYNC_RTC_IPM_IN, mbox_callback, user_data);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
return mbox_set_enabled(dev, CONFIG_NRF53_SYNC_RTC_IPM_IN, true);
|
|
}
|
|
|
|
/* Setup RTC synchronization. */
|
|
static int sync_rtc_setup(void)
|
|
{
|
|
nrfx_err_t err;
|
|
union rtc_sync_channels channels;
|
|
int32_t sync_rtc_ch;
|
|
int rv;
|
|
|
|
err = nrfx_dppi_channel_alloc(&channels.ch.ppi);
|
|
if (err != NRFX_SUCCESS) {
|
|
rv = -ENODEV;
|
|
goto bail;
|
|
}
|
|
|
|
sync_rtc_ch = z_nrf_rtc_timer_chan_alloc();
|
|
if (sync_rtc_ch < 0) {
|
|
nrfx_dppi_channel_free(channels.ch.ppi);
|
|
rv = sync_rtc_ch;
|
|
goto bail;
|
|
}
|
|
|
|
channels.ch.rtc = (uint8_t)sync_rtc_ch;
|
|
channels.ch.ipc_out = CONFIG_NRF53_SYNC_RTC_IPM_OUT;
|
|
channels.ch.ipc_in = CONFIG_NRF53_SYNC_RTC_IPM_IN;
|
|
|
|
rv = mbox_rx_init((void *)channels.raw);
|
|
if (rv < 0) {
|
|
goto bail;
|
|
}
|
|
|
|
nrfx_gppi_channels_enable(BIT(channels.ch.ppi));
|
|
|
|
if (IS_ENABLED(CONFIG_SOC_COMPATIBLE_NRF5340_CPUAPP)) {
|
|
ppi_ipc_to_rtc(channels, true);
|
|
} else {
|
|
ppi_rtc_to_ipc(channels, true);
|
|
|
|
uint32_t key = irq_lock();
|
|
|
|
sync_cc = z_nrf_rtc_timer_read() + RTC_SYNC_ARBITRARY_DELAY;
|
|
z_nrf_rtc_timer_set(channels.ch.rtc, sync_cc, rtc_cb, (void *)channels.raw);
|
|
irq_unlock(key);
|
|
}
|
|
|
|
bail:
|
|
if (rv != 0) {
|
|
LOG_ERR("Failed synchronized RTC setup (err: %d)", rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
#if defined(CONFIG_MBOX_INIT_PRIORITY)
|
|
BUILD_ASSERT(CONFIG_NRF53_SYNC_RTC_INIT_PRIORITY > CONFIG_MBOX_INIT_PRIORITY,
|
|
"RTC Sync must be initialized after MBOX driver.");
|
|
#endif
|
|
|
|
SYS_INIT(sync_rtc_setup, POST_KERNEL, CONFIG_NRF53_SYNC_RTC_INIT_PRIORITY);
|