icE1usb fw: Add USB control for the GPSDO function

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Change-Id: If92aa68d8a49349fa0d6d556eec81bbb80be989c
diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.c b/firmware/ice40-riscv/icE1usb/gpsdo.c
index ca9b806..ae131d4 100644
--- a/firmware/ice40-riscv/icE1usb/gpsdo.c
+++ b/firmware/ice40-riscv/icE1usb/gpsdo.c
@@ -13,6 +13,8 @@
 #include "gpsdo.h"
 #include "misc.h"
 
+#include "ice1usb_proto.h"
+
 
 struct {
 	/* Current tuning */
@@ -74,6 +76,72 @@
 #define MAX_INVALID	5
 
 
+static void _gpsdo_coarse_start(void);
+
+
+void
+gpsdo_get_status(struct e1usb_gpsdo_status *status)
+{
+	const uint8_t state_map[] = {
+		[STATE_DISABLED]    = ICE1USB_GPSDO_STATE_DISABLED,
+		[STATE_HOLD_OVER]   = ICE1USB_GPSDO_STATE_HOLD_OVER,
+		[STATE_TUNE_COARSE] = ICE1USB_GPSDO_STATE_TUNE_COARSE,
+		[STATE_TUNE_FINE]   = ICE1USB_GPSDO_STATE_TUNE_FINE,
+	};
+	const uint8_t ant_map[] = {
+		[ANT_UNKNOWN] = ICE1USB_GPSDO_ANT_UNKNOWN,
+		[ANT_OK]      = ICE1USB_GPSDO_ANT_OK,
+		[ANT_OPEN]    = ICE1USB_GPSDO_ANT_OPEN,
+		[ANT_SHORT]   = ICE1USB_GPSDO_ANT_SHORT,
+	};
+
+	status->state          = state_map[g_gpsdo.state];
+	status->antenna_status = ant_map[gps_antenna_status()];
+	status->valid_fix      = gps_has_valid_fix();
+	status->mode           = (g_gpsdo.state == STATE_DISABLED) ? ICE1USB_GPSDO_MODE_DISABLED : ICE1USB_GPSDO_MODE_AUTO;
+	status->tune.coarse    = g_gpsdo.tune.coarse;
+	status->tune.fine      = g_gpsdo.tune.fine;
+	status->freq_est       = g_gpsdo.meas.last;
+}
+
+void
+gpsdo_enable(bool enable)
+{
+	if (!enable)
+		g_gpsdo.state = STATE_DISABLED;
+	else if (g_gpsdo.state == STATE_DISABLED)
+		g_gpsdo.state = STATE_HOLD_OVER;
+}
+
+bool
+gpsdo_enabled(void)
+{
+	return g_gpsdo.state != STATE_DISABLED;
+}
+
+void
+gpsdo_set_tune(uint16_t  coarse, uint16_t  fine)
+{
+	/* Set value */
+	g_gpsdo.tune.coarse = coarse;
+	g_gpsdo.tune.fine   = fine;
+
+	pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
+	pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine,   false);
+
+	/* If we were in 'fine' mode, reset to coarse */
+	if (g_gpsdo.state == STATE_TUNE_FINE)
+		_gpsdo_coarse_start();
+}
+
+void
+gpsdo_get_tune(uint16_t *coarse, uint16_t *fine)
+{
+	*coarse = g_gpsdo.tune.coarse;
+	*fine   = g_gpsdo.tune.fine;
+}
+
+
 static void
 _gpsdo_coarse_start(void)
 {
diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.h b/firmware/ice40-riscv/icE1usb/gpsdo.h
index 181e4b9..a771db7 100644
--- a/firmware/ice40-riscv/icE1usb/gpsdo.h
+++ b/firmware/ice40-riscv/icE1usb/gpsdo.h
@@ -12,5 +12,16 @@
 	VCTXO_SITIME_SIT3808_E = 1,	/* SIT3808AI-D2-33EE-30.720000T */
 };
 
+struct e1usb_gpsdo_status;
+
+
+void gpsdo_get_status(struct e1usb_gpsdo_status *status);
+
+void gpsdo_enable(bool enable);
+bool gpsdo_enabled(void);
+
+void gpsdo_set_tune(uint16_t  coarse, uint16_t  fine);
+void gpsdo_get_tune(uint16_t *coarse, uint16_t *fine);
+
 void gpsdo_poll(void);
 void gpsdo_init(enum gpsdo_vctxo_model vctxo);
diff --git a/firmware/ice40-riscv/icE1usb/ice1usb_proto.h b/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
index 9ac2136..a249537 100644
--- a/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
+++ b/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
@@ -17,12 +17,51 @@
 /*! returns a bit-mask of optional device capabilities (see enum e1usb_dev_capability) */
 #define ICE1USB_DEV_GET_CAPABILITIES	0x01
 #define ICE1USB_DEV_GET_FW_BUILD	0x02
+#define ICE1USB_DEV_GET_GPSDO_STATUS	0x10
+#define ICE1USB_DEV_GET_GPSDO_MODE	0x12	/*!< uint8_t */
+#define ICE1USB_DEV_SET_GPSDO_MODE	0x13	/*!< wValue = mode */
+#define ICE1USB_DEV_GET_GPSDO_TUNE	0x14	/*!< data   = struct e1usb_gpsdo_tune */
+#define ICE1USB_DEV_SET_GPSDO_TUNE	0x15	/*!< data   = struct e1usb_gpsdo_tune */
 
 enum e1usb_dev_capability {
 	/*! Does this board have a GPS-DO */
 	ICE1USB_DEV_CAP_GPSDO,
 };
 
+enum ice1usb_gpsdo_mode {
+	ICE1USB_GPSDO_MODE_DISABLED	= 0,
+	ICE1USB_GPSDO_MODE_AUTO		= 1,
+};
+
+enum ice1usb_gpsdo_antenna_state {
+	ICE1USB_GPSDO_ANT_UNKNOWN	= 0,
+	ICE1USB_GPSDO_ANT_OK		= 1,
+	ICE1USB_GPSDO_ANT_OPEN		= 2,
+	ICE1USB_GPSDO_ANT_SHORT		= 3,
+};
+
+enum ice1usb_gpsdo_state {
+	ICE1USB_GPSDO_STATE_DISABLED	= 0,
+	ICE1USB_GPSDO_STATE_CALIBRATE	= 1,
+	ICE1USB_GPSDO_STATE_HOLD_OVER	= 2,
+	ICE1USB_GPSDO_STATE_TUNE_COARSE	= 3,
+	ICE1USB_GPSDO_STATE_TUNE_FINE	= 4,
+};
+
+struct e1usb_gpsdo_tune {
+	uint16_t coarse;
+	uint16_t fine;
+} __attribute__((packed));
+
+struct e1usb_gpsdo_status {
+	uint8_t state;
+	uint8_t antenna_status;		/*!< Antenna status */
+	uint8_t valid_fix;		/*!< Valid GPS Fix (0/1) */
+	uint8_t mode;			/*!< Current configured operating mode */
+	struct e1usb_gpsdo_tune tune;	/*!< Current VCXO tuning values */
+	uint32_t freq_est;		/*!< Latest frequency estimate measurement */
+} __attribute__((packed));
+
 
 /* Interface Requests */
 
diff --git a/firmware/ice40-riscv/icE1usb/usb_dev.c b/firmware/ice40-riscv/icE1usb/usb_dev.c
index e19d9a0..2997a33 100644
--- a/firmware/ice40-riscv/icE1usb/usb_dev.c
+++ b/firmware/ice40-riscv/icE1usb/usb_dev.c
@@ -12,6 +12,7 @@
 #include <no2usb/usb_proto.h>
 
 #include "console.h"
+#include "gpsdo.h"
 #include "misc.h"
 
 #include "ice1usb_proto.h"
@@ -20,6 +21,61 @@
 const char *fw_build_str = BUILD_INFO;
 
 
+static void
+_get_gpsdo_status(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	struct e1usb_gpsdo_status status;
+
+	gpsdo_get_status(&status);
+
+	memcpy(xfer->data, &status, sizeof(struct e1usb_gpsdo_status));
+	xfer->len = sizeof(struct e1usb_gpsdo_status);
+}
+
+static void
+_get_gpsdo_mode(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	xfer->data[0] = gpsdo_enabled() ? ICE1USB_GPSDO_MODE_DISABLED : ICE1USB_GPSDO_MODE_AUTO;
+	xfer->len = 1;
+}
+
+static void
+_set_gpsdo_mode(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	gpsdo_enable(req->wValue != ICE1USB_GPSDO_MODE_DISABLED);
+}
+
+static void
+_get_gpsdo_tune(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	uint16_t coarse, fine;
+	struct e1usb_gpsdo_tune tune;
+
+	gpsdo_get_tune(&coarse, &fine);
+	tune.coarse = coarse;
+	tune.fine = fine;
+
+	memcpy(xfer->data, &tune, sizeof(struct e1usb_gpsdo_tune));
+	xfer->len = sizeof(struct e1usb_gpsdo_tune);
+}
+
+static bool
+_set_gpsdo_tune_done(struct usb_xfer *xfer)
+{
+	const struct e1usb_gpsdo_tune *tune = (const void *) xfer->data;
+	gpsdo_set_tune(tune->coarse, tune->fine);
+	return true;
+}
+
+static void
+_set_gpsdo_tune(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	xfer->cb_done = _set_gpsdo_tune_done;
+	xfer->cb_ctx  = req;
+	xfer->len     = sizeof(struct e1usb_gpsdo_tune);
+}
+
+
 static enum usb_fnd_resp
 _usb_dev_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
 {
@@ -37,6 +93,21 @@
 		xfer->data = (void*) fw_build_str;
 		xfer->len  = strlen(fw_build_str);
 		break;
+	case ICE1USB_DEV_GET_GPSDO_STATUS:
+		_get_gpsdo_status(req, xfer);
+		break;
+	case ICE1USB_DEV_GET_GPSDO_MODE:
+		_get_gpsdo_mode(req, xfer);
+		break;
+	case ICE1USB_DEV_SET_GPSDO_MODE:
+		_set_gpsdo_mode(req, xfer);
+		break;
+	case ICE1USB_DEV_GET_GPSDO_TUNE:
+		_get_gpsdo_tune(req, xfer);
+		break;
+	case ICE1USB_DEV_SET_GPSDO_TUNE:
+		_set_gpsdo_tune(req, xfer);
+		break;
 	default:
 		return USB_FND_ERROR;
 	}