icE1usb: Move GPS-DO USB control to separate USB interface

doing so significantly simplifies the development of a Linux kernel
driver, as the GPS-DO only exists once (not twice, like the per-E1-line
interface), and Linux kernel USB drivers typically are for an interface.

There is an option to write a usb_device_driver, but doing so will
exclude the per-interface drivers from still being probed in their usual
fashion.

While we introduce this new USB Interface for the GPS-DO, we also
move the related control endpoint requests from the device level to the
interface level.

Finally, some naming inconsistency between "enum
ice1usb_gpsdo_antenna_state" vs. the member name antenna_status is
resolved.

Change-Id: Icd6555a14896c38626fb147b78af44ff719f2254
diff --git a/firmware/ice40-riscv/icE1usb/Makefile b/firmware/ice40-riscv/icE1usb/Makefile
index 25c65d1..ed765f6 100644
--- a/firmware/ice40-riscv/icE1usb/Makefile
+++ b/firmware/ice40-riscv/icE1usb/Makefile
@@ -56,6 +56,7 @@
 	usb_dev.h \
 	usb_e1.h \
 	usb_gps.h \
+	usb_gpsdo.h \
 	usb_str_app.gen.h \
 	$(NULL)
 
@@ -69,6 +70,7 @@
 	usb_dev.c \
 	usb_e1.c \
 	usb_gps.c \
+	usb_gpsdo.c \
 	$(NULL)
 
 
diff --git a/firmware/ice40-riscv/icE1usb/fw_app.c b/firmware/ice40-riscv/icE1usb/fw_app.c
index c1fe295..540eda2 100644
--- a/firmware/ice40-riscv/icE1usb/fw_app.c
+++ b/firmware/ice40-riscv/icE1usb/fw_app.c
@@ -23,6 +23,7 @@
 #include "usb_dev.h"
 #include "usb_e1.h"
 #include "usb_gps.h"
+#include "usb_gpsdo.h"
 #include "utils.h"
 
 
@@ -109,6 +110,7 @@
 	usb_dfu_rt_init();
 	usb_e1_init();
 	usb_gps_init();
+	usb_gpsdo_init();
 
 	/* Start */
 	led_state(true);
diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.c b/firmware/ice40-riscv/icE1usb/gpsdo.c
index ae131d4..8e32627 100644
--- a/firmware/ice40-riscv/icE1usb/gpsdo.c
+++ b/firmware/ice40-riscv/icE1usb/gpsdo.c
@@ -96,7 +96,7 @@
 	};
 
 	status->state          = state_map[g_gpsdo.state];
-	status->antenna_status = ant_map[gps_antenna_status()];
+	status->antenna_state  = 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;
diff --git a/firmware/ice40-riscv/icE1usb/ice1usb_proto.h b/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
index a249537..3170d55 100644
--- a/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
+++ b/firmware/ice40-riscv/icE1usb/ice1usb_proto.h
@@ -17,17 +17,22 @@
 /*! 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,
 };
 
+/***********************************************************************
+ * Control Endpoint / GPS-DO Interface Requests
+ ***********************************************************************/
+
+#define ICE1USB_INTF_GET_GPSDO_STATUS	0x10
+#define ICE1USB_INTF_GET_GPSDO_MODE	0x12	/*!< uint8_t */
+#define ICE1USB_INTF_SET_GPSDO_MODE	0x13	/*!< wValue = mode */
+#define ICE1USB_INTF_GET_GPSDO_TUNE	0x14	/*!< data   = struct e1usb_gpsdo_tune */
+#define ICE1USB_INTF_SET_GPSDO_TUNE	0x15	/*!< data   = struct e1usb_gpsdo_tune */
+
 enum ice1usb_gpsdo_mode {
 	ICE1USB_GPSDO_MODE_DISABLED	= 0,
 	ICE1USB_GPSDO_MODE_AUTO		= 1,
@@ -55,7 +60,7 @@
 
 struct e1usb_gpsdo_status {
 	uint8_t state;
-	uint8_t antenna_status;		/*!< Antenna status */
+	uint8_t antenna_state;		/*!< Antenna state */
 	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 */
@@ -63,7 +68,9 @@
 } __attribute__((packed));
 
 
-/* Interface Requests */
+/***********************************************************************
+ * Control Endpoint / E1 Interface Requests
+ ***********************************************************************/
 
 /*! returns a bit-mask of optional device capabilities (see enum e1usb_intf_capability) */
 #define ICE1USB_INTF_GET_CAPABILITIES	0x01
diff --git a/firmware/ice40-riscv/icE1usb/usb_desc_app.c b/firmware/ice40-riscv/icE1usb/usb_desc_app.c
index e0edf80..25472e0 100644
--- a/firmware/ice40-riscv/icE1usb/usb_desc_app.c
+++ b/firmware/ice40-riscv/icE1usb/usb_desc_app.c
@@ -53,6 +53,11 @@
 		struct usb_ep_desc ep_data_in;
 	} __attribute__ ((packed)) cdc;
 
+	/* GPS-DO (control EP only) */
+	struct {
+		struct usb_intf_desc intf;
+	} __attribute__ ((packed)) gpsdo;
+
 	/* DFU Runtime */
 	struct {
 		struct usb_intf_desc intf;
@@ -275,6 +280,19 @@
 			.bInterval		= 0x00,
 		},
 	},
+	.gpsdo = {
+		.intf = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= USB_INTF_GPSDO,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 0,
+			.bInterfaceClass	= 0xff,
+			.bInterfaceSubClass	= 0xe1,
+			.bInterfaceProtocol	= 0xd0,
+			.iInterface		= 11,
+		}
+	},
 	.dfu = {
 		.intf = {
 			.bLength		= sizeof(struct usb_intf_desc),
@@ -285,7 +303,7 @@
 			.bInterfaceClass	= 0xfe,
 			.bInterfaceSubClass	= 0x01,
 			.bInterfaceProtocol	= 0x01,
-			.iInterface		= 11,
+			.iInterface		= 12,
 		},
 		.func = {
 			.bLength		= sizeof(struct usb_dfu_func_desc),
diff --git a/firmware/ice40-riscv/icE1usb/usb_desc_ids.h b/firmware/ice40-riscv/icE1usb/usb_desc_ids.h
index dadf4c6..192092e 100644
--- a/firmware/ice40-riscv/icE1usb/usb_desc_ids.h
+++ b/firmware/ice40-riscv/icE1usb/usb_desc_ids.h
@@ -10,8 +10,9 @@
 #define USB_INTF_E1(p)		(0 + (p))
 #define USB_INTF_GPS_CDC_CTL	2
 #define USB_INTF_GPS_CDC_DATA	3
-#define USB_INTF_DFU		4
-#define USB_INTF_NUM		5
+#define USB_INTF_GPSDO		4
+#define USB_INTF_DFU		5
+#define USB_INTF_NUM		6
 
 #define USB_EP_E1_IN(p)		(0x82 + (3 * (p)))
 #define USB_EP_E1_OUT(p)	(0x01 + (3 * (p)))
diff --git a/firmware/ice40-riscv/icE1usb/usb_dev.c b/firmware/ice40-riscv/icE1usb/usb_dev.c
index 2997a33..e19d9a0 100644
--- a/firmware/ice40-riscv/icE1usb/usb_dev.c
+++ b/firmware/ice40-riscv/icE1usb/usb_dev.c
@@ -12,7 +12,6 @@
 #include <no2usb/usb_proto.h>
 
 #include "console.h"
-#include "gpsdo.h"
 #include "misc.h"
 
 #include "ice1usb_proto.h"
@@ -21,61 +20,6 @@
 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)
 {
@@ -93,21 +37,6 @@
 		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;
 	}
diff --git a/firmware/ice40-riscv/icE1usb/usb_gpsdo.c b/firmware/ice40-riscv/icE1usb/usb_gpsdo.c
new file mode 100644
index 0000000..4dd8913
--- /dev/null
+++ b/firmware/ice40-riscv/icE1usb/usb_gpsdo.c
@@ -0,0 +1,120 @@
+/*
+ * usb_gpsdo.c
+ *
+ * Copyright (C) 2019-2022  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <no2usb/usb.h>
+#include <no2usb/usb_hw.h>
+#include <no2usb/usb_priv.h>
+
+#include <no2usb/usb_proto.h>
+
+#include "usb_desc_ids.h"
+#include "gpsdo.h"
+
+#include "ice1usb_proto.h"
+
+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
+_gpsdo_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	/* Check it's for an interface */
+	if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_VENDOR | USB_REQ_RCPT_INTF))
+		return USB_FND_CONTINUE;
+
+	/* Check it's for the GPS-DO interface */
+	if (req->wIndex != USB_INTF_GPSDO)
+		return USB_FND_CONTINUE;
+
+	switch (req->bRequest) {
+	case ICE1USB_INTF_GET_GPSDO_STATUS:
+		_get_gpsdo_status(req, xfer);
+		break;
+	case ICE1USB_INTF_GET_GPSDO_MODE:
+		_get_gpsdo_mode(req, xfer);
+		break;
+	case ICE1USB_INTF_SET_GPSDO_MODE:
+		_set_gpsdo_mode(req, xfer);
+		break;
+	case ICE1USB_INTF_GET_GPSDO_TUNE:
+		_get_gpsdo_tune(req, xfer);
+		break;
+	case ICE1USB_INTF_SET_GPSDO_TUNE:
+		_set_gpsdo_tune(req, xfer);
+		break;
+	default:
+		return USB_FND_ERROR;
+	}
+
+	return USB_FND_SUCCESS;
+}
+
+static struct usb_fn_drv _gpsdo_drv = {
+	.ctrl_req	= _gpsdo_ctrl_req,
+};
+
+void
+usb_gpsdo_init(void)
+{
+	usb_register_function_driver(&_gpsdo_drv);
+}
diff --git a/firmware/ice40-riscv/icE1usb/usb_gpsdo.h b/firmware/ice40-riscv/icE1usb/usb_gpsdo.h
new file mode 100644
index 0000000..4cf4203
--- /dev/null
+++ b/firmware/ice40-riscv/icE1usb/usb_gpsdo.h
@@ -0,0 +1,10 @@
+/*
+ * usb_gpsdo.h
+ *
+ * Copyright (C) 2022  Harald Welte <laforge@osmocom.org>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+void usb_gpsdo_init(void);
diff --git a/firmware/ice40-riscv/icE1usb/usb_str_app.txt b/firmware/ice40-riscv/icE1usb/usb_str_app.txt
index 3aa6b4a..5f062d3 100644
--- a/firmware/ice40-riscv/icE1usb/usb_str_app.txt
+++ b/firmware/ice40-riscv/icE1usb/usb_str_app.txt
@@ -8,4 +8,5 @@
 E1 port 1
 GPS (CDC control)
 GPS (CDC data)
+GPS-DO control
 DFU runtime