/* * Copyright (c) 2021 Nordic Semiconductor ASA. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include 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);