blob: ae131d4b4cc0d86bd1dea92fbd5eab8af3c4e5ce [file] [log] [blame]
Sylvain Munaute76b6432022-01-12 22:41:01 +01001/*
2 * gpsdo.c
3 *
4 * Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
5 * SPDX-License-Identifier: GPL-3.0-or-later
6 */
7
8#include <stdint.h>
9#include <string.h>
10
11#include "console.h"
12#include "gps.h"
13#include "gpsdo.h"
14#include "misc.h"
15
Sylvain Munautc3083342022-01-13 00:50:10 +010016#include "ice1usb_proto.h"
17
Sylvain Munaute76b6432022-01-12 22:41:01 +010018
19struct {
20 /* Current tuning */
21 struct {
22 uint16_t coarse;
23 uint16_t fine;
24 } tune;
25
26 /* Measurements */
27 struct {
28 uint32_t tick_prev; /* Previous tick value */
29 uint32_t last; /* Last measurement */
30 int skip; /* Number of measurement left to discard */
31 int invalid; /* Number of consecutive invalid measurements */
32 } meas;
33
34 /* FSM */
35 enum {
36 STATE_DISABLED, /* Forced to manual */
37 STATE_HOLD_OVER, /* GPS invalid data, hold */
38 STATE_TUNE_COARSE, /* Adjust coarse tuning until we're +- 3 Hz */
39 STATE_TUNE_FINE, /* Fine tracking */
40 } state;
41
42 /* Coarse tuning */
43 struct {
44 int vctxo_sensitivity;
45 int step;
46 } coarse;
47
48 /* Fine tracking */
49 struct {
50 int acc;
51 int div;
52 } fine;
53
54} g_gpsdo;
55
56
57/* VCXTO sensitivity vs 'coarse' count for fast initial acquisition */
58 /*
59 * Note that the spec if often a guaranteed minimum range and goes
60 * from ~0.1V to 3.2V instead of 0-3.3V so actual sensitivity is
61 * higher than the "theoritical value". We boost it by ~ 10% here.
62 */
63static const int
64vctxo_sensitivity[] = {
65 /* +- 50 ppm pull range => ~ 0.75 Hz / hi-count (set to 0.85) */
66 [VCTXO_TAITIEN_VT40] = 300,
67
68 /* +- 100 ppm pull range => ~ 1.50 Hz / hi-count (set to 1.6) */
69 [VCTXO_SITIME_SIT3808_E] = 160,
70};
71
72/* Tuning params */
73#define TARGET 30720000
74#define MAX_DEV_VALID 1000
75#define MAX_DEV_FINE 3
76#define MAX_INVALID 5
77
78
Sylvain Munautc3083342022-01-13 00:50:10 +010079static void _gpsdo_coarse_start(void);
80
81
82void
83gpsdo_get_status(struct e1usb_gpsdo_status *status)
84{
85 const uint8_t state_map[] = {
86 [STATE_DISABLED] = ICE1USB_GPSDO_STATE_DISABLED,
87 [STATE_HOLD_OVER] = ICE1USB_GPSDO_STATE_HOLD_OVER,
88 [STATE_TUNE_COARSE] = ICE1USB_GPSDO_STATE_TUNE_COARSE,
89 [STATE_TUNE_FINE] = ICE1USB_GPSDO_STATE_TUNE_FINE,
90 };
91 const uint8_t ant_map[] = {
92 [ANT_UNKNOWN] = ICE1USB_GPSDO_ANT_UNKNOWN,
93 [ANT_OK] = ICE1USB_GPSDO_ANT_OK,
94 [ANT_OPEN] = ICE1USB_GPSDO_ANT_OPEN,
95 [ANT_SHORT] = ICE1USB_GPSDO_ANT_SHORT,
96 };
97
98 status->state = state_map[g_gpsdo.state];
99 status->antenna_status = ant_map[gps_antenna_status()];
100 status->valid_fix = gps_has_valid_fix();
101 status->mode = (g_gpsdo.state == STATE_DISABLED) ? ICE1USB_GPSDO_MODE_DISABLED : ICE1USB_GPSDO_MODE_AUTO;
102 status->tune.coarse = g_gpsdo.tune.coarse;
103 status->tune.fine = g_gpsdo.tune.fine;
104 status->freq_est = g_gpsdo.meas.last;
105}
106
107void
108gpsdo_enable(bool enable)
109{
110 if (!enable)
111 g_gpsdo.state = STATE_DISABLED;
112 else if (g_gpsdo.state == STATE_DISABLED)
113 g_gpsdo.state = STATE_HOLD_OVER;
114}
115
116bool
117gpsdo_enabled(void)
118{
119 return g_gpsdo.state != STATE_DISABLED;
120}
121
122void
123gpsdo_set_tune(uint16_t coarse, uint16_t fine)
124{
125 /* Set value */
126 g_gpsdo.tune.coarse = coarse;
127 g_gpsdo.tune.fine = fine;
128
129 pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
130 pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false);
131
132 /* If we were in 'fine' mode, reset to coarse */
133 if (g_gpsdo.state == STATE_TUNE_FINE)
134 _gpsdo_coarse_start();
135}
136
137void
138gpsdo_get_tune(uint16_t *coarse, uint16_t *fine)
139{
140 *coarse = g_gpsdo.tune.coarse;
141 *fine = g_gpsdo.tune.fine;
142}
143
144
Sylvain Munaute76b6432022-01-12 22:41:01 +0100145static void
146_gpsdo_coarse_start(void)
147{
148 /* Set the state */
149 g_gpsdo.state = STATE_TUNE_COARSE;
150
151 /* Skip a few measurements to be safe */
152 g_gpsdo.meas.skip = 3;
153
154 /* Reset coarse tuning state */
155 g_gpsdo.coarse.step = 0;
156
157 /* Put the fine adjust back in the middle point */
158 g_gpsdo.tune.coarse += ((int)g_gpsdo.tune.fine - 2048) >> 6;
159 g_gpsdo.tune.fine = 2048;
160
161 pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
162 pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false);
163}
164
165static void
166_gpsdo_fine_start(void)
167{
168 /* Set the state */
169 g_gpsdo.state = STATE_TUNE_FINE;
170
171 /* Reset the long term error tracking */
172 g_gpsdo.fine.acc = 0;
173 g_gpsdo.fine.div = 0;
174}
175
176static void
177_gpsdo_coarse_tune(uint32_t tick_diff)
178{
179 int freq_diff = (int)tick_diff - TARGET;
180
181 /* Is the measurement good enough to switch to fine ? */
182 if ((freq_diff > -MAX_DEV_FINE) && (freq_diff < MAX_DEV_FINE)) {
183 _gpsdo_fine_start();
184 return;
185 }
186
187 /* Estimate correction and apply it */
188 g_gpsdo.tune.coarse -= (freq_diff * g_gpsdo.coarse.vctxo_sensitivity) >> 8;
189 pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
190
191 /* Skip next measurement */
192 g_gpsdo.meas.skip = 1;
193
194 /* Debug */
195#ifdef GPSDO_DEBUG
196 printf("[+] GPSDO Coarse: err=%d tune=%d\n", freq_diff, g_gpsdo.tune.coarse);
197#endif
198}
199
200static void
201_gpsdo_fine_track(uint32_t tick_diff)
202{
203 int freq_diff = (int)tick_diff - TARGET;
204 uint16_t tune;
205
206 /* Did we deviate too much ? */
207 if ((freq_diff < -2*MAX_DEV_FINE) && (freq_diff > 2*MAX_DEV_FINE)) {
208 _gpsdo_coarse_start();
209 return;
210 }
211
212 /* Accumulate long term error */
213 g_gpsdo.fine.acc += freq_diff;
214
215 /* Update fine tuning value */
216 if (((g_gpsdo.fine.acc >= 0) && (freq_diff > 0)) ||
217 ((g_gpsdo.fine.acc <= 0) && (freq_diff < 0)))
218 {
219 g_gpsdo.tune.fine -= freq_diff;
220 }
221
222 if (g_gpsdo.fine.acc) {
223 if (++g_gpsdo.fine.div > 10) {
224 g_gpsdo.fine.div = 0;
225 if (g_gpsdo.fine.acc > 0)
226 g_gpsdo.tune.fine--;
227 else if (g_gpsdo.fine.acc < 0)
228 g_gpsdo.tune.fine++;
229 }
230 } else {
231 g_gpsdo.fine.div = 0;
232 }
233
234 /* Apply value with a bias from long term accumulator */
235 tune = g_gpsdo.tune.fine - (g_gpsdo.fine.acc / 2);
236 pdm_set(PDM_CLK_LO, true, tune, false);
237
238 /* Debug */
239#ifdef GPSDO_DEBUG
240 printf("[+] GPSDO Fine: err=%d acc=%d tune=%d\n", freq_diff, g_gpsdo.fine.acc, g_gpsdo.tune.fine);
241#endif
242}
243
244
245void
246gpsdo_poll(void)
247{
248 uint32_t tick_now, tick_diff;
249 bool valid;
250
251 /* Get current tick and check if there was a PPS and estimate frequency */
252 tick_now = time_pps_read();
253
254 if (tick_now == g_gpsdo.meas.tick_prev)
255 return;
256
257 g_gpsdo.meas.last = tick_diff = tick_now - g_gpsdo.meas.tick_prev;
258 g_gpsdo.meas.tick_prev = tick_now;
259
260 /* If we're currently discarding samples, skip it */
261 if (g_gpsdo.meas.skip) {
262 g_gpsdo.meas.skip--;
263 return;
264 }
265
266 /* If we're disabled, nothing else to do */
267 if (g_gpsdo.state == STATE_DISABLED)
268 return;
269
270 /* Check GPS state */
271 if (!gps_has_valid_fix()) {
272 /* No GPS fix, go to hold-over */
273 g_gpsdo.state = STATE_HOLD_OVER;
274 return;
275 }
276
277 /* Check measurement plausibility */
278 valid = (tick_diff > (TARGET - MAX_DEV_VALID)) && (tick_diff < (TARGET + MAX_DEV_VALID));
279
280 if (valid) {
281 /* If we're in hold-over, switch to active */
282 if (g_gpsdo.state == STATE_HOLD_OVER) {
283 _gpsdo_coarse_start();
284 return;
285 }
286 } else {
287 /* Count invalid measurements and if too many of
288 * them, we go back to hold-over */
289 if (++g_gpsdo.meas.invalid >= MAX_INVALID) {
290 g_gpsdo.state = STATE_HOLD_OVER;
291 return;
292 }
293 }
294
295 g_gpsdo.meas.invalid = 0;
296
297 /* If we reach here, we have a valid fix, valid measurement and
298 * we're not in hold-over or disabled. Feed the correct loop */
299 if (g_gpsdo.state == STATE_TUNE_COARSE)
300 _gpsdo_coarse_tune(tick_diff);
301 else if (g_gpsdo.state == STATE_TUNE_FINE)
302 _gpsdo_fine_track(tick_diff);
303}
304
305
306void
307gpsdo_init(enum gpsdo_vctxo_model vctxo)
308{
309 /* State */
310 memset(&g_gpsdo, 0x00, sizeof(g_gpsdo));
311
312 /* Default tune to middle range */
313 g_gpsdo.tune.coarse = 2048;
314 g_gpsdo.tune.fine = 2048;
315
316 pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
317 pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false);
318
319 /* Initial state and config */
320 g_gpsdo.state = STATE_HOLD_OVER;
321 g_gpsdo.coarse.vctxo_sensitivity = vctxo_sensitivity[vctxo];
322}