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