|
| 1 | +/* |
| 2 | + * Copyright (c) 2020 Nordic Semiconductor ASA |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <stdio.h> |
| 8 | +#include <zephyr.h> |
| 9 | +#include <sys/timeutil.h> |
| 10 | +#include <drivers/clock_control.h> |
| 11 | +#include <drivers/clock_control/nrf_clock_control.h> |
| 12 | +#include <drivers/counter.h> |
| 13 | +#include <nrfx_clock.h> |
| 14 | + |
| 15 | +#define TIMER_NODE DT_NODELABEL(timer0) |
| 16 | +#define CLOCK_NODE DT_INST(0, nordic_nrf_clock) |
| 17 | +#define UPDATE_INTERVAL_S 10 |
| 18 | + |
| 19 | +static const struct device *clock0; |
| 20 | +static const struct device *timer0; |
| 21 | +static struct timeutil_sync_config sync_config; |
| 22 | +static uint64_t counter_ref; |
| 23 | +static struct timeutil_sync_state sync_state; |
| 24 | +static struct k_delayed_work sync_work; |
| 25 | + |
| 26 | +/* Convert local time in ticks to microseconds. */ |
| 27 | +uint64_t local_to_us(uint64_t local) |
| 28 | +{ |
| 29 | + return z_tmcvt(local, sync_config.local_Hz, USEC_PER_SEC, false, |
| 30 | + false, false, false); |
| 31 | +} |
| 32 | + |
| 33 | +/* Convert HFCLK reference to microseconds. */ |
| 34 | +uint64_t ref_to_us(uint64_t ref) |
| 35 | +{ |
| 36 | + return z_tmcvt(ref, sync_config.ref_Hz, USEC_PER_SEC, false, |
| 37 | + false, false, false); |
| 38 | +} |
| 39 | + |
| 40 | +/* Format a microsecond timestamp to text as D d HH:MM:SS.SSSSSS. */ |
| 41 | +static const char *us_to_text_r(uint64_t rem, char *buf, size_t len) |
| 42 | +{ |
| 43 | + char *bp = buf; |
| 44 | + char *bpe = bp + len; |
| 45 | + uint32_t us; |
| 46 | + uint32_t s; |
| 47 | + uint32_t min; |
| 48 | + uint32_t hr; |
| 49 | + uint32_t d; |
| 50 | + |
| 51 | + us = rem % USEC_PER_SEC; |
| 52 | + rem /= USEC_PER_SEC; |
| 53 | + s = rem % 60; |
| 54 | + rem /= 60; |
| 55 | + min = rem % 60; |
| 56 | + rem /= 60; |
| 57 | + hr = rem % 24; |
| 58 | + rem /= 24; |
| 59 | + d = rem; |
| 60 | + |
| 61 | + if (d > 0) { |
| 62 | + bp += snprintf(bp, bpe - bp, "%u d ", d); |
| 63 | + } |
| 64 | + bp += snprintf(bp, bpe - bp, "%02u:%02u:%02u.%06u", |
| 65 | + hr, min, s, us); |
| 66 | + return buf; |
| 67 | +} |
| 68 | + |
| 69 | +static const char *us_to_text(uint64_t rem) |
| 70 | +{ |
| 71 | + static char ts_buf[32]; |
| 72 | + |
| 73 | + return us_to_text_r(rem, ts_buf, sizeof(ts_buf)); |
| 74 | +} |
| 75 | + |
| 76 | +/* Show status of various clocks */ |
| 77 | +static void show_clocks(const char *tag) |
| 78 | +{ |
| 79 | + static const char *const lfsrc_s[] = { |
| 80 | +#if defined(CLOCK_LFCLKSRC_SRC_LFULP) |
| 81 | + [NRF_CLOCK_LFCLK_LFULP] = "LFULP", |
| 82 | +#endif |
| 83 | + [NRF_CLOCK_LFCLK_RC] = "LFRC", |
| 84 | + [NRF_CLOCK_LFCLK_Xtal] = "LFXO", |
| 85 | + [NRF_CLOCK_LFCLK_Synth] = "LFSYNT", |
| 86 | + }; |
| 87 | + static const char *const hfsrc_s[] = { |
| 88 | + [NRF_CLOCK_HFCLK_LOW_ACCURACY] = "HFINT", |
| 89 | + [NRF_CLOCK_HFCLK_HIGH_ACCURACY] = "HFXO", |
| 90 | + }; |
| 91 | + static const char *const clkstat_s[] = { |
| 92 | + [CLOCK_CONTROL_STATUS_STARTING] = "STARTING", |
| 93 | + [CLOCK_CONTROL_STATUS_OFF] = "OFF", |
| 94 | + [CLOCK_CONTROL_STATUS_ON] = "ON", |
| 95 | + [CLOCK_CONTROL_STATUS_UNKNOWN] = "UNKNOWN", |
| 96 | + }; |
| 97 | + union { |
| 98 | + unsigned int raw; |
| 99 | + nrf_clock_lfclk_t lf; |
| 100 | + nrf_clock_hfclk_t hf; |
| 101 | + } src; |
| 102 | + enum clock_control_status clkstat; |
| 103 | + bool running; |
| 104 | + |
| 105 | + clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_LF); |
| 106 | + running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_LFCLK, |
| 107 | + &src.lf); |
| 108 | + printk("%s: LFCLK[%s]: %s %s ; ", tag, clkstat_s[clkstat], |
| 109 | + running ? "Running" : "Off", lfsrc_s[src.lf]); |
| 110 | + clkstat = clock_control_get_status(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF); |
| 111 | + running = nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_HFCLK, |
| 112 | + &src.hf); |
| 113 | + printk("HFCLK[%s]: %s %s\n", clkstat_s[clkstat], |
| 114 | + running ? "Running" : "Off", hfsrc_s[src.hf]); |
| 115 | +} |
| 116 | + |
| 117 | +static void sync_work_handler(struct k_work *work) |
| 118 | +{ |
| 119 | + uint32_t ctr; |
| 120 | + int rc = counter_get_value(timer0, &ctr); |
| 121 | + const struct timeutil_sync_instant *base = &sync_state.base; |
| 122 | + const struct timeutil_sync_instant *latest = &sync_state.latest; |
| 123 | + |
| 124 | + if (rc == 0) { |
| 125 | + struct timeutil_sync_instant inst; |
| 126 | + uint64_t ref_span_us; |
| 127 | + |
| 128 | + counter_ref += ctr - (uint32_t)counter_ref; |
| 129 | + inst.ref = counter_ref; |
| 130 | + inst.local = k_uptime_ticks(); |
| 131 | + |
| 132 | + rc = timeutil_sync_state_update(&sync_state, &inst); |
| 133 | + printf("\nTy Latest Base Span Err\n"); |
| 134 | + printf("HF %s", us_to_text(ref_to_us(inst.ref))); |
| 135 | + if (rc > 0) { |
| 136 | + printf(" %s", us_to_text(ref_to_us(base->ref))); |
| 137 | + ref_span_us = ref_to_us(latest->ref - base->ref); |
| 138 | + printf(" %s", us_to_text(ref_span_us)); |
| 139 | + } |
| 140 | + printf("\nLF %s", us_to_text(local_to_us(inst.local))); |
| 141 | + if (rc > 0) { |
| 142 | + uint64_t err_us; |
| 143 | + uint64_t local_span_us; |
| 144 | + char err_sign = ' '; |
| 145 | + |
| 146 | + printf(" %s", us_to_text(local_to_us(base->local))); |
| 147 | + |
| 148 | + local_span_us = local_to_us(latest->local - base->local); |
| 149 | + printf(" %s", us_to_text(local_span_us)); |
| 150 | + |
| 151 | + if (ref_span_us >= local_span_us) { |
| 152 | + err_us = ref_span_us - local_span_us; |
| 153 | + err_sign = '-'; |
| 154 | + } else { |
| 155 | + err_us = local_span_us - ref_span_us; |
| 156 | + } |
| 157 | + printf(" %c%s", err_sign, us_to_text(err_us)); |
| 158 | + } |
| 159 | + printf("\n"); |
| 160 | + if (rc > 0) { |
| 161 | + float skew = timeutil_sync_estimate_skew(&sync_state); |
| 162 | + |
| 163 | + printf("Skew %f ; err %d ppb\n", skew, |
| 164 | + timeutil_sync_skew_to_ppb(skew)); |
| 165 | + } else if (rc < 0) { |
| 166 | + printf("Sync update error: %d\n", rc); |
| 167 | + } |
| 168 | + } |
| 169 | + k_delayed_work_submit(&sync_work, K_SECONDS(UPDATE_INTERVAL_S)); |
| 170 | +} |
| 171 | + |
| 172 | +void main(void) |
| 173 | +{ |
| 174 | + const char *clock_label = DT_LABEL(CLOCK_NODE); |
| 175 | + const char *timer0_label = DT_LABEL(TIMER_NODE); |
| 176 | + uint32_t top; |
| 177 | + int rc; |
| 178 | + |
| 179 | + /* Grab the clock driver */ |
| 180 | + clock0 = device_get_binding(clock_label); |
| 181 | + if (clock0 == NULL) { |
| 182 | + printk("Failed to fetch clock %s\n", clock_label); |
| 183 | + } |
| 184 | + |
| 185 | + show_clocks("Power-up clocks"); |
| 186 | + |
| 187 | + if (IS_ENABLED(CONFIG_APP_ENABLE_HFXO)) { |
| 188 | + rc = clock_control_on(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF); |
| 189 | + printk("Enable HFXO got %d\n", rc); |
| 190 | + } |
| 191 | + |
| 192 | + /* Grab the timer. */ |
| 193 | + timer0 = device_get_binding(timer0_label); |
| 194 | + if (timer0 == NULL) { |
| 195 | + printk("Failed to fetch timer0 %s\n", timer0_label); |
| 196 | + return; |
| 197 | + } |
| 198 | + |
| 199 | + /* Apparently there's no API to configure a frequency at |
| 200 | + * runtime, so live with whatever we get. |
| 201 | + */ |
| 202 | + sync_config.ref_Hz = counter_get_frequency(timer0); |
| 203 | + if (sync_config.ref_Hz == 0) { |
| 204 | + printk("Timer %s has no fixed frequency\n", |
| 205 | + timer0_label); |
| 206 | + return; |
| 207 | + } |
| 208 | + |
| 209 | + top = counter_get_top_value(timer0); |
| 210 | + if (top != UINT32_MAX) { |
| 211 | + printk("Timer %s wraps at %u (0x%08x) not at 32 bits\n", |
| 212 | + timer0_label, top, top); |
| 213 | + return; |
| 214 | + } |
| 215 | + |
| 216 | + rc = counter_start(timer0); |
| 217 | + printk("Start %s: %d\n", timer0_label, rc); |
| 218 | + |
| 219 | + show_clocks("Timer-running clocks"); |
| 220 | + |
| 221 | + sync_config.local_Hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC; |
| 222 | + |
| 223 | + sync_state.cfg = &sync_config; |
| 224 | + |
| 225 | + printf("Checking %s at %u Hz against ticks at %u Hz\n", |
| 226 | + timer0_label, sync_config.ref_Hz, sync_config.local_Hz); |
| 227 | + printf("Timer wraps every %u s\n", |
| 228 | + (uint32_t)(BIT64(32) / sync_config.ref_Hz)); |
| 229 | + |
| 230 | + k_delayed_work_init(&sync_work, sync_work_handler); |
| 231 | + rc = k_delayed_work_submit(&sync_work, K_NO_WAIT); |
| 232 | + |
| 233 | + printk("Started sync: %d\n", rc); |
| 234 | +} |
0 commit comments