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_work_delayable sync_work;
25 
26 /* Convert local time in ticks to microseconds. */
local_to_us(uint64_t local)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. */
ref_to_us(uint64_t ref)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. */
us_to_text_r(uint64_t rem,char * buf,size_t len)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 
us_to_text(uint64_t rem)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 */
show_clocks(const char * tag)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 
sync_work_handler(struct k_work * work)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 			/* Create a state with the current skew estimate.  Use
164 			 * it to reconstruct the expected reference time from
165 			 * the latest local time, then display that time and
166 			 * its error from the latest reference time.
167 			 */
168 			uint64_t rec_ref;
169 			struct timeutil_sync_state st2 = sync_state;
170 
171 			(void)timeutil_sync_state_set_skew(&st2, skew, NULL);
172 			(void)timeutil_sync_ref_from_local(&st2, latest->local,
173 							   &rec_ref);
174 
175 			char err_sign = ' ';
176 			uint64_t err_us;
177 
178 			if (rec_ref < latest->ref) {
179 				err_sign = '-';
180 				err_us = ref_to_us(latest->ref - rec_ref);
181 			} else {
182 				err_us = ref_to_us(rec_ref - latest->ref);
183 			}
184 
185 			printf("RHF %s                                   ",
186 			       us_to_text(ref_to_us(rec_ref)));
187 			printf("%c%s\n", err_sign, us_to_text(err_us));
188 
189 			printf("Skew %f ; err %d ppb\n", skew,
190 			       timeutil_sync_skew_to_ppb(skew));
191 		} else if (rc < 0) {
192 			printf("Sync update error: %d\n", rc);
193 		}
194 	}
195 	(void)k_work_schedule(k_work_delayable_from_work(work),
196 			      K_SECONDS(UPDATE_INTERVAL_S));
197 }
198 
main(void)199 void main(void)
200 {
201 	const char *clock_label = DT_LABEL(CLOCK_NODE);
202 	const char *timer0_label = DT_LABEL(TIMER_NODE);
203 	uint32_t top;
204 	int rc;
205 
206 	/* Grab the clock driver */
207 	clock0 = device_get_binding(clock_label);
208 	if (clock0 == NULL) {
209 		printk("Failed to fetch clock %s\n", clock_label);
210 	}
211 
212 	show_clocks("Power-up clocks");
213 
214 	if (IS_ENABLED(CONFIG_APP_ENABLE_HFXO)) {
215 		rc = clock_control_on(clock0, CLOCK_CONTROL_NRF_SUBSYS_HF);
216 		printk("Enable HFXO got %d\n", rc);
217 	}
218 
219 	/* Grab the timer. */
220 	timer0 = device_get_binding(timer0_label);
221 	if (timer0 == NULL) {
222 		printk("Failed to fetch timer0 %s\n", timer0_label);
223 		return;
224 	}
225 
226 	/* Apparently there's no API to configure a frequency at
227 	 * runtime, so live with whatever we get.
228 	 */
229 	sync_config.ref_Hz = counter_get_frequency(timer0);
230 	if (sync_config.ref_Hz == 0) {
231 		printk("Timer %s has no fixed frequency\n",
232 			timer0_label);
233 		return;
234 	}
235 
236 	top = counter_get_top_value(timer0);
237 	if (top != UINT32_MAX) {
238 		printk("Timer %s wraps at %u (0x%08x) not at 32 bits\n",
239 		       timer0_label, top, top);
240 		return;
241 	}
242 
243 	rc = counter_start(timer0);
244 	printk("Start %s: %d\n", timer0_label, rc);
245 
246 	show_clocks("Timer-running clocks");
247 
248 	sync_config.local_Hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC;
249 
250 	sync_state.cfg = &sync_config;
251 
252 	printf("Checking %s at %u Hz against ticks at %u Hz\n",
253 	       timer0_label, sync_config.ref_Hz, sync_config.local_Hz);
254 	printf("Timer wraps every %u s\n",
255 	       (uint32_t)(BIT64(32) / sync_config.ref_Hz));
256 
257 	k_work_init_delayable(&sync_work, sync_work_handler);
258 	rc = k_work_schedule(&sync_work, K_NO_WAIT);
259 
260 	printk("Started sync: %d\n", rc);
261 }
262