/* * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ /** This file implements controller time management for 52 Series devices * * As the controller clock is not directly accessible, the controller * time is obtained using a mirrored RTC peripheral. * To achieve microsecond accurate toggling, a timer peripheral is also used. */ // BK: taken from ncs/nrf/samples/bluetooth/conn_time_sync/src/controller_time_nrf52.c #include #include #include #include #include #include #include #include #include "controller_time.h" static const nrfx_rtc_t app_rtc_instance = NRFX_RTC_INSTANCE(2); static const nrfx_timer_t app_timer_instance = NRFX_TIMER_INSTANCE(1); static uint8_t ppi_chan_on_rtc_match; static volatile uint32_t num_rtc_overflows; static uint32_t offset_ticks_and_controller_to_app_rtc; static void rtc_isr_handler(nrfx_rtc_int_type_t int_type) { if (int_type == NRFX_RTC_INT_OVERFLOW) { num_rtc_overflows++; } } static void unused_timer_isr_handler(nrf_timer_event_t event_type, void *ctx) { ARG_UNUSED(event_type); ARG_UNUSED(ctx); } static int32_t rtc_diff_get(void) { uint32_t controller_ticks = nrf_rtc_counter_get(NRF_RTC0); uint32_t app_ticks = nrf_rtc_counter_get(app_rtc_instance.p_reg); return controller_ticks - app_ticks; } static int rtc_config(void) { int ret; const nrfx_rtc_config_t rtc_cfg = NRFX_RTC_DEFAULT_CONFIG; ret = nrfx_rtc_init(&app_rtc_instance, &rtc_cfg, rtc_isr_handler); if (ret != NRFX_SUCCESS) { printk("Failed initializing RTC (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM); return -ENODEV; } #ifndef BT_CTLR_SDC_BSIM_BUILD IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(2)), IRQ_PRIO_LOWEST, NRFX_RTC_INST_HANDLER_GET(2), NULL, 0); #else IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_RTC_INST_GET(2)), 0, (void *)NRFX_RTC_INST_HANDLER_GET(2), NULL, 0); #endif nrfx_rtc_overflow_enable(&app_rtc_instance, true); nrfx_rtc_tick_enable(&app_rtc_instance, false); nrfx_rtc_enable(&app_rtc_instance); /* To obtain the controller timestamp without modifying the state of RTC0, * we find the offset between RTC0 and our mirrored RTC. * We achieve this by reading out the counter value of both of them. * When the diff between those two have been equal twice, we know the * time difference in ticks. */ uint32_t sync_attempts = 10; while (sync_attempts > 0) { sync_attempts--; int32_t diff_measurement_1 = rtc_diff_get(); /* We need to wait half an RTC tick to ensure we are not measuring * the diff between the two RTCs at the point in time where their * values are transitioning. */ k_busy_wait(15); int32_t diff_measurement_2 = rtc_diff_get(); if (diff_measurement_1 == diff_measurement_2) { offset_ticks_and_controller_to_app_rtc = diff_measurement_1; return 0; } } printk("Controller time sync failure\n"); offset_ticks_and_controller_to_app_rtc = 0; return -EINVAL; } static int timer_config(void) { int ret; uint8_t ppi_chan_timer_clear_on_rtc_tick; const nrfx_timer_config_t timer_cfg = { .frequency = NRFX_MHZ_TO_HZ(1UL), .mode = NRF_TIMER_MODE_TIMER, .bit_width = NRF_TIMER_BIT_WIDTH_8, .interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY, .p_context = NULL}; ret = nrfx_timer_init(&app_timer_instance, &timer_cfg, unused_timer_isr_handler); if (ret != NRFX_SUCCESS) { printk("Failed initializing timer (ret: %d)\n", ret - NRFX_ERROR_BASE_NUM); return -ENODEV; } /* Clear the TIMER every RTC tick. */ if (nrfx_gppi_channel_alloc(&ppi_chan_timer_clear_on_rtc_tick) != NRFX_SUCCESS) { printk("Failed allocating for clearing TIMER on RTC TICK\n"); return -ENOMEM; } nrfx_gppi_channel_endpoints_setup(ppi_chan_timer_clear_on_rtc_tick, nrfx_rtc_event_address_get(&app_rtc_instance, NRF_RTC_EVENT_TICK), nrfx_timer_task_address_get(&app_timer_instance, NRF_TIMER_TASK_CLEAR)); nrfx_gppi_channels_enable(BIT(ppi_chan_timer_clear_on_rtc_tick)); nrfx_timer_enable(&app_timer_instance); return 0; } /** Configure the TIMER and RTC in such a way that microsecond accurate timing can be achieved. * * To get microsecond accurate toggling, we need to combine both the RTC and TIMER peripheral. * That is, both a RTC CC value and TIMER CC value needs to be set. * We should only trigger an EGU task when the TIMER CC value matches after the * RTC CC value matched. * This is achieved using a PPI group. The group is enabled when the RTC CC matches and disabled * again then the TIMER CC matches. */ int config_egu_trigger_on_rtc_and_timer_match(void) { uint8_t ppi_chan_on_timer_match; if (nrfx_gppi_channel_alloc(&ppi_chan_on_rtc_match) != NRFX_SUCCESS) { printk("Failed allocating for RTC match\n"); return -ENOMEM; } if (nrfx_gppi_channel_alloc(&ppi_chan_on_timer_match) != NRFX_SUCCESS) { printk("Failed allocating for TIMER match\n"); return -ENOMEM; } nrfx_gppi_group_clear(NRFX_GPPI_CHANNEL_GROUP0); nrfx_gppi_group_disable(NRFX_GPPI_CHANNEL_GROUP0); nrfx_gppi_channels_include_in_group( BIT(ppi_chan_on_timer_match) | BIT(ppi_chan_on_rtc_match), NRFX_GPPI_CHANNEL_GROUP0); nrfx_gppi_channel_endpoints_setup(ppi_chan_on_rtc_match, nrfx_rtc_event_address_get(&app_rtc_instance, NRF_RTC_EVENT_COMPARE_0), nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_EN)); nrfx_gppi_channel_endpoints_setup(ppi_chan_on_timer_match, nrfx_timer_event_address_get(&app_timer_instance, NRF_TIMER_EVENT_COMPARE0), nrf_egu_task_address_get(NRF_EGU0, NRF_EGU_TASK_TRIGGER0)); nrfx_gppi_fork_endpoint_setup(ppi_chan_on_timer_match, nrfx_gppi_task_address_get(NRFX_GPPI_TASK_CHG0_DIS)); return 0; } int controller_time_init(void) { int ret; ret = rtc_config(); if (ret) { return ret; } ret = timer_config(); if (ret) { return ret; } return config_egu_trigger_on_rtc_and_timer_match(); } static uint64_t rtc_ticks_to_us(uint32_t rtc_ticks) { const uint64_t rtc_ticks_in_femto_units = 30517578125UL; return (rtc_ticks * rtc_ticks_in_femto_units) / 1000000000UL; } static uint32_t us_to_rtc_ticks(uint32_t timestamp_us) { const uint64_t rtc_ticks_in_femto_units = 30517578125UL; return ((uint64_t)(timestamp_us) * 1000000000UL) / rtc_ticks_in_femto_units; } uint64_t controller_time_us_get(void) { const uint64_t rtc_overflow_time_us = 512000000UL; /* On the 52 series the RTC has to task to capture the current RTC value. * Therefore we cannot capture the TIMER and RTC value simultaneously. * Therefore we only use the RTC to read out the current time here. * This will result in an error of maximum one RTC tick. */ uint32_t captured_rtc_overflows; uint32_t captured_rtc_ticks; while (true) { captured_rtc_overflows = num_rtc_overflows; /* Read out RTC ticks after reading number of overflows. */ barrier_isync_fence_full(); captured_rtc_ticks = nrf_rtc_counter_get(app_rtc_instance.p_reg); /* Read out number of overflows after reading number of RTC ticks */ barrier_isync_fence_full(); if (captured_rtc_overflows == num_rtc_overflows) { /* There were no new overflows after reading ticks. * That is, we can use the captured value. */ break; } } return rtc_ticks_to_us(captured_rtc_ticks) + (captured_rtc_overflows * rtc_overflow_time_us) + rtc_ticks_to_us(offset_ticks_and_controller_to_app_rtc); } void controller_time_trigger_set(uint64_t timestamp_us) { uint64_t timestamp_without_rtc_offset = timestamp_us - rtc_ticks_to_us(offset_ticks_and_controller_to_app_rtc); uint32_t num_overflows = timestamp_without_rtc_offset / 512000000UL; uint64_t overflow_time_us = num_overflows * 512000000UL; uint32_t rtc_remainder_time_us = timestamp_without_rtc_offset - overflow_time_us; uint32_t rtc_val = us_to_rtc_ticks(rtc_remainder_time_us); uint8_t timer_val = timestamp_without_rtc_offset - rtc_ticks_to_us(rtc_val); /* Ensure the timer value lies between 1 and 30 so that it will * always be between two RTC ticks. */ timer_val = MAX(timer_val, 1); timer_val = MIN(timer_val, 30); if (nrfx_rtc_cc_set(&app_rtc_instance, 0, rtc_val, false) != NRFX_SUCCESS) { printk("Failed setting trigger\n"); } nrfx_timer_compare(&app_timer_instance, 0, timer_val, false); nrfx_gppi_channels_enable(BIT(ppi_chan_on_rtc_match)); } uint32_t controller_time_trigger_event_addr_get(void) { return nrf_egu_event_address_get(NRF_EGU0, NRF_EGU_EVENT_TRIGGERED0); } SYS_INIT(controller_time_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);