blob: 724453583b3535269845fa4b20b0ee89a0558f0f [file] [log] [blame]
/*
* 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_dppi_t dppi = NRFX_DPPI_INSTANCE(0);
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(&dppi, 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_dppi_t dppi = NRFX_DPPI_INSTANCE(0);
nrfx_err_t err;
union rtc_sync_channels channels;
int32_t sync_rtc_ch;
int rv;
err = nrfx_dppi_channel_alloc(&dppi, &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(&dppi, 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);