| /* |
| * 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); |