icE1usb fw/gpsdo: Limit the fine tuning range

Fine tuning has a limited tuning range. If at some point we
hit the limits, we need to bit the bullet and try to 'transfer'
some of that to the coarse range as best as we can. Hopefully
we get it close enough to limit disruption.

Note that this should really never happen because although it's
limited, the tuning range should be good enough to absorb any
reasonable temperature / aging variation once we have coarse tuned.

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Change-Id: I2d9d348f5466f581b3d6d36c98847c47e2452f98
diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.c b/firmware/ice40-riscv/icE1usb/gpsdo.c
index 057490f..c1df4b0 100644
--- a/firmware/ice40-riscv/icE1usb/gpsdo.c
+++ b/firmware/ice40-riscv/icE1usb/gpsdo.c
@@ -240,8 +240,23 @@
 		g_gpsdo.fine.div = 0;
 	}
 
-	/* Apply value with a bias from long term accumulator */
+	/* Compute value with a bias from long term accumulator */
 	tune = g_gpsdo.tune.fine - (g_gpsdo.fine.acc / 2);
+
+	/* If fine tune is getting close to boundary, do our
+	 * best to transfer part of it to coarse tuning */
+	if ((g_gpsdo.tune.fine < 512) || (g_gpsdo.tune.fine > 3584))
+	{
+		int coarse_adj = ((int)g_gpsdo.tune.fine - 2048) >> 6;
+
+		g_gpsdo.tune.coarse += coarse_adj;
+		g_gpsdo.tune.fine   -= coarse_adj << 6;
+		tune                -= coarse_adj << 6;
+
+		pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
+	}
+
+	/* Apply fine */
 	pdm_set(PDM_CLK_LO, true, tune, false);
 
 	/* Debug */