linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/3] bluetooth/gnss: GNSS support for TiWi chips
@ 2024-01-28 17:33 Andreas Kemnade
  2024-01-28 17:33 ` [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips Andreas Kemnade
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-28 17:33 UTC (permalink / raw)
  To: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, andreas,
	gregkh, linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Some of these chips have GNSS support. In some vendor kernels
a driver on top of misc/ti-st can be found providing a /dev/tigps
device which speaks the secretive Air Independent Interface (AI2) protocol.

To be more compatible with userspace send out NMEA by default but
allow a more raw mode by using a module parameter.

This was tested on the Epson Moverio BT-200.

Changes since V1:
- Set up things for NMEA output
- Powerup/down at open()/close()
- split out logic between drivers/bluetooth and drivers/gnss
- leave out drivers/misc/ti-st driver removal to avoid
  filling up mailboxes during the iterations, this series is
  still a proof that it is not needed, will take the brush after
  this series is accepted.

Andreas Kemnade (3):
  gnss: Add AI2 protocol used by some TI combo chips.
  bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips
  gnss: Add driver for AI2 protocol

 drivers/bluetooth/hci_ll.c   |  81 ++++++
 drivers/gnss/Kconfig         |  13 +
 drivers/gnss/Makefile        |   3 +
 drivers/gnss/ai2.c           | 523 +++++++++++++++++++++++++++++++++++
 drivers/gnss/core.c          |   1 +
 include/linux/gnss.h         |   1 +
 include/linux/ti_wilink_st.h |   8 +
 7 files changed, 630 insertions(+)
 create mode 100644 drivers/gnss/ai2.c

-- 
2.39.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips.
  2024-01-28 17:33 [RFC PATCH v2 0/3] bluetooth/gnss: GNSS support for TiWi chips Andreas Kemnade
@ 2024-01-28 17:33 ` Andreas Kemnade
  2024-01-29  7:40   ` Paul Menzel
  2024-01-28 17:33 ` [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips Andreas Kemnade
  2024-01-28 17:33 ` [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Andreas Kemnade
  2 siblings, 1 reply; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-28 17:33 UTC (permalink / raw)
  To: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, andreas,
	gregkh, linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Texas Instruments uses something called Air Independent Interface (AI2) for
their WLAN/BT/GPS combo chips.
No public documentation is available, but allow that protocol to be
specified.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/gnss/core.c  | 1 +
 include/linux/gnss.h | 1 +
 2 files changed, 2 insertions(+)

diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c
index 48f2ee0f78c4d..cac9f45aec4b2 100644
--- a/drivers/gnss/core.c
+++ b/drivers/gnss/core.c
@@ -335,6 +335,7 @@ static const char * const gnss_type_names[GNSS_TYPE_COUNT] = {
 	[GNSS_TYPE_SIRF]	= "SiRF",
 	[GNSS_TYPE_UBX]		= "UBX",
 	[GNSS_TYPE_MTK]		= "MTK",
+	[GNSS_TYPE_AI2]		= "AI2",
 };
 
 static const char *gnss_type_name(const struct gnss_device *gdev)
diff --git a/include/linux/gnss.h b/include/linux/gnss.h
index 36968a0f33e8d..16b565dab83ea 100644
--- a/include/linux/gnss.h
+++ b/include/linux/gnss.h
@@ -23,6 +23,7 @@ enum gnss_type {
 	GNSS_TYPE_SIRF,
 	GNSS_TYPE_UBX,
 	GNSS_TYPE_MTK,
+	GNSS_TYPE_AI2,
 
 	GNSS_TYPE_COUNT
 };
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips
  2024-01-28 17:33 [RFC PATCH v2 0/3] bluetooth/gnss: GNSS support for TiWi chips Andreas Kemnade
  2024-01-28 17:33 ` [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips Andreas Kemnade
@ 2024-01-28 17:33 ` Andreas Kemnade
  2024-01-29  7:40   ` Paul Menzel
  2024-01-28 17:33 ` [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Andreas Kemnade
  2 siblings, 1 reply; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-28 17:33 UTC (permalink / raw)
  To: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, andreas,
	gregkh, linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Some of these chips have GNSS support. GNSS support
is available through channel 9 whilst FM is through channel 8.
Add a platform subdevice for GNSS so that a driver for that
functionality can be build. To avoid having useless GNSS
devices, do it only when the devicetree node namecontains gnss.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/bluetooth/hci_ll.c   | 81 ++++++++++++++++++++++++++++++++++++
 include/linux/ti_wilink_st.h |  8 ++++
 2 files changed, 89 insertions(+)

diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c
index 4a0b5c3160c2b..09e5a4dbd2f8c 100644
--- a/drivers/bluetooth/hci_ll.c
+++ b/drivers/bluetooth/hci_ll.c
@@ -32,6 +32,7 @@
 #include <linux/signal.h>
 #include <linux/ioctl.h>
 #include <linux/of.h>
+#include <linux/platform_device.h>
 #include <linux/serdev.h>
 #include <linux/skbuff.h>
 #include <linux/ti_wilink_st.h>
@@ -68,6 +69,9 @@ struct ll_device {
 	struct gpio_desc *enable_gpio;
 	struct clk *ext_clk;
 	bdaddr_t bdaddr;
+
+	void (*gnss_recv_func)(struct device *dev, struct sk_buff *skb);
+	struct platform_device *gnssdev;
 };
 
 struct ll_struct {
@@ -78,6 +82,8 @@ struct ll_struct {
 	struct sk_buff_head tx_wait_q;	/* HCILL wait queue	*/
 };
 
+static int ll_gnss_register(struct ll_device *lldev);
+static int ll_gnss_recv_frame(struct hci_dev *hdev, struct sk_buff *skb);
 /*
  * Builds and sends an HCILL command packet.
  * These are very simple packets with only 1 cmd byte
@@ -411,6 +417,13 @@ static int ll_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
 	.lsize = 0, \
 	.maxlen = 0
 
+#define LL_RECV_GNSS \
+	.type = 9, \
+	.hlen = 3, \
+	.loff = 1, \
+	.lsize = 2
+
+
 static const struct h4_recv_pkt ll_recv_pkts[] = {
 	{ H4_RECV_ACL,       .recv = hci_recv_frame },
 	{ H4_RECV_SCO,       .recv = hci_recv_frame },
@@ -419,6 +432,7 @@ static const struct h4_recv_pkt ll_recv_pkts[] = {
 	{ LL_RECV_SLEEP_ACK, .recv = ll_recv_frame  },
 	{ LL_RECV_WAKE_IND,  .recv = ll_recv_frame  },
 	{ LL_RECV_WAKE_ACK,  .recv = ll_recv_frame  },
+	{ LL_RECV_GNSS,      .recv = ll_gnss_recv_frame },
 };
 
 /* Recv data */
@@ -677,9 +691,69 @@ static int ll_setup(struct hci_uart *hu)
 		}
 	}
 
+	if (strstr(of_node_full_name(serdev->dev.of_node), "gnss"))
+		ll_gnss_register(lldev);
+
+	return 0;
+}
+
+struct hci_dev *st_get_hci(struct device *dev)
+{
+	struct ll_device *lldev = dev_get_drvdata(dev);
+
+	return lldev->hu.hdev;
+}
+EXPORT_SYMBOL(st_get_hci);
+
+void st_set_gnss_recv_func(struct device *dev,
+			   void (*recv_frame)(struct device *, struct sk_buff *))
+{
+	struct ll_device *lldev = dev_get_drvdata(dev);
+
+	lldev->gnss_recv_func = recv_frame;
+}
+EXPORT_SYMBOL(st_set_gnss_recv_func);
+
+static int ll_gnss_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	struct ll_device *lldev = container_of(hu, struct ll_device, hu);
+
+	if (!lldev->gnssdev)
+		return 0;
+
+	if (lldev->gnss_recv_func) {
+		lldev->gnss_recv_func(&lldev->gnssdev->dev, skb);
+		return 0;
+	}
+	kfree_skb(skb);
+
 	return 0;
 }
 
+static int ll_gnss_register(struct ll_device *lldev)
+{
+	struct platform_device *pdev;
+	int ret;
+
+	pdev = platform_device_alloc("ti-ai2-gnss", PLATFORM_DEVID_AUTO);
+	if (!pdev)
+		return -ENOMEM;
+
+	pdev->dev.parent = &lldev->serdev->dev;
+	lldev->gnssdev = pdev;
+	ret = platform_device_add(pdev);
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	lldev->gnssdev = NULL;
+	platform_device_put(pdev);
+	return ret;
+}
+
 static const struct hci_uart_proto llp;
 
 static int hci_ti_probe(struct serdev_device *serdev)
@@ -757,12 +831,19 @@ static int hci_ti_probe(struct serdev_device *serdev)
 	}
 
 	return hci_uart_register_device(hu, &llp);
+
+
+	return 0;
 }
 
+
 static void hci_ti_remove(struct serdev_device *serdev)
 {
 	struct ll_device *lldev = serdev_device_get_drvdata(serdev);
 
+	if (lldev->gnssdev)
+		platform_device_unregister(lldev->gnssdev);
+
 	hci_uart_unregister_device(&lldev->hu);
 }
 
diff --git a/include/linux/ti_wilink_st.h b/include/linux/ti_wilink_st.h
index 10642d4844f0c..eccc2db004069 100644
--- a/include/linux/ti_wilink_st.h
+++ b/include/linux/ti_wilink_st.h
@@ -381,6 +381,14 @@ unsigned long st_ll_getstate(struct st_data_s *);
 unsigned long st_ll_sleep_state(struct st_data_s *, unsigned char);
 void st_ll_wakeup(struct st_data_s *);
 
+/**
+ * various funcs used to interact between FM, GPS and BT
+ */
+struct hci_dev *st_get_hci(struct device *dev);
+void st_set_gnss_recv_func(struct device *dev,
+			   void (*recv_frame)(struct device *, struct sk_buff *));
+
+
 /*
  * header information used by st_core.c for FM and GPS
  * packet parsing, the bluetooth headers are already available
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-01-28 17:33 [RFC PATCH v2 0/3] bluetooth/gnss: GNSS support for TiWi chips Andreas Kemnade
  2024-01-28 17:33 ` [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips Andreas Kemnade
  2024-01-28 17:33 ` [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips Andreas Kemnade
@ 2024-01-28 17:33 ` Andreas Kemnade
  2024-01-29  7:53   ` Paul Menzel
  2024-02-13 13:57   ` Adam Ford
  2 siblings, 2 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-28 17:33 UTC (permalink / raw)
  To: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, andreas,
	gregkh, linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Add a driver for the Air Independent Interface protocol used by some TI
Wilink combo chips. Per default, send out just NMEA to userspace and turn
on/off things at open()/close() but keep the door open for any
sophisticated development regarding the AI2 protocol by having a kernel
parameter to turn it into raw mode resembling /dev/tigps provided by some
TI vendor kernels.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/gnss/Kconfig  |  13 ++
 drivers/gnss/Makefile |   3 +
 drivers/gnss/ai2.c    | 523 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 539 insertions(+)
 create mode 100644 drivers/gnss/ai2.c

diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
index d7fe265c28696..3a20212dacc9e 100644
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -65,4 +65,17 @@ config GNSS_USB
 
 	  If unsure, say N.
 
+config GNSS_AI2
+	tristate "TI AI2 procotol support"
+	depends on BT_HCIUART_LL
+	help
+	  Say Y here if you have a Texas Instruments Wilink combo chip
+	  contaning among other things a GNSS receiver speaking the
+	  Air Independent Interface (AI2) protocol.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called gnss-ai2.
+
+	  If unsure, say N.
+
 endif # GNSS
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
index bb2cbada34359..bf6fefcb2e823 100644
--- a/drivers/gnss/Makefile
+++ b/drivers/gnss/Makefile
@@ -20,3 +20,6 @@ gnss-ubx-y := ubx.o
 
 obj-$(CONFIG_GNSS_USB)			+= gnss-usb.o
 gnss-usb-y := usb.o
+
+obj-$(CONFIG_GNSS_AI2)			+= gnss-ai2.o
+gnss-ai2-y := ai2.o
diff --git a/drivers/gnss/ai2.c b/drivers/gnss/ai2.c
new file mode 100644
index 0000000000000..673fbe8de7ef2
--- /dev/null
+++ b/drivers/gnss/ai2.c
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Texas Instruments AI2 (Air independent interface) protocol device driver
+ * Used for some TI WLAN/Bluetooth/GNSS combo chips.
+ *
+ * Copyright (C) 2024 Andreas Kemnade <andreas@kemnade.info>
+ */
+#include <linux/errno.h>
+#include <linux/gnss.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/ti_wilink_st.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+/* Channel-9 details for GPS */
+#define GPS_CH9_PKT_NUMBER		0x9
+#define GPS_CH9_OP_WRITE		0x1
+#define GPS_CH9_OP_READ			0x2
+#define GPS_CH9_OP_COMPLETED_EVT	0x3
+
+/* arbitarily chosen, should fit everything seen in the past */
+#define MAX_AI2_FRAME_SIZE 2048
+
+#define AI2_ESCAPE 0x10 /* if sent as data, it is doubled */
+#define AI2_END_MARKER 0x3
+#define AI2_ACK 0x2
+
+/* reports */
+#define AI2_REPORT_NMEA 0xd3
+
+#define NMEA_HEADER_LEN 4
+
+/* commands */
+#define AI2_CMD_RECEIVER_STATE 2
+
+#define RECEIVER_STATE_OFF 1
+#define RECEIVER_STATE_IDLE 2
+#define RECEIVER_STATE_ON 3
+
+#define AI2_CMD_CONFIG_NMEA 0xe5
+#define NMEA_MASK_GGA (1 << 0)
+#define NMEA_MASK_GLL (1 << 1)
+#define NMEA_MASK_GSA (1 << 2)
+#define NMEA_MASK_GSV (1 << 3)
+#define NMEA_MASK_RMC (1 << 4)
+#define NMEA_MASK_VTG (1 << 5)
+
+#define NMEA_MASK_ALL (NMEA_MASK_GGA | \
+		NMEA_MASK_GLL | \
+		NMEA_MASK_GSA | \
+		NMEA_MASK_GSV | \
+		NMEA_MASK_RMC | \
+		NMEA_MASK_VTG)
+
+
+static bool ai2raw;
+
+struct ai2_device {
+	struct mutex gdev_mutex;
+	bool gdev_open;
+	struct gnss_device *gdev;
+	struct device *dev;
+	struct sk_buff *recv_skb;
+	bool recv_esc;
+};
+
+static struct sk_buff *ai2_skb_alloc(unsigned int len, gfp_t how)
+{
+	struct sk_buff *skb;
+
+	skb = bt_skb_alloc(len + sizeof(struct gps_event_hdr), how);
+	if (skb)
+		skb_reserve(skb, sizeof(struct gps_event_hdr));
+
+	return skb;
+}
+
+static int ai2_send_frame(struct ai2_device *ai2dev,
+			  struct sk_buff *skb)
+{
+	int len;
+	struct gps_event_hdr *gnssdrv_hdr;
+	struct hci_dev *hdev;
+
+	if (skb->len >= U16_MAX)
+		return -EINVAL;
+
+	/*
+	 * note: fragmentation at this point not handled yet
+	 * not needed for simple config commands
+	 */
+	len = skb->len;
+	gnssdrv_hdr = skb_push(skb, sizeof(struct gps_event_hdr));
+	gnssdrv_hdr->opcode = GPS_CH9_OP_WRITE;
+	gnssdrv_hdr->plen = __cpu_to_le16(len);
+
+	hci_skb_pkt_type(skb) = GPS_CH9_PKT_NUMBER;
+	hdev = st_get_hci(ai2dev->dev->parent);
+	return hdev->send(hdev, skb);
+}
+
+static void ai2_put_escaped(struct sk_buff *skb, u8 d)
+{
+	skb_put_u8(skb, d);
+	if (d == 0x10)
+		skb_put_u8(skb, d);
+}
+
+static struct sk_buff *ai2_compose_frame(bool request_ack,
+					u8 cmd,
+					const u8 *data,
+					int len)
+{
+	u16 sum;
+	int i;
+	/* duplicate the length to have space for worst case escaping */
+	struct sk_buff *skb = ai2_skb_alloc(2 + len * 2 + 2 + 2, GFP_KERNEL);
+
+	skb_put_u8(skb, AI2_ESCAPE);
+	skb_put_u8(skb, request_ack ? 1 : 0);
+
+	sum = AI2_ESCAPE;
+	if (request_ack)
+		sum++;
+
+	ai2_put_escaped(skb, cmd);
+	sum += cmd;
+
+	ai2_put_escaped(skb, len & 0xff);
+	sum += len & 0xff;
+
+	ai2_put_escaped(skb, len >> 8);
+	sum += len >> 8;
+
+	for (i = 0; i < len; i++) {
+		sum += data[i];
+		ai2_put_escaped(skb, data[i]);
+	}
+
+	ai2_put_escaped(skb, sum & 0xFF);
+	ai2_put_escaped(skb, sum >> 8);
+	skb_put_u8(skb, AI2_ESCAPE);
+	skb_put_u8(skb, AI2_END_MARKER);
+
+	return skb;
+}
+
+static int ai2_set_receiver_state(struct ai2_device *ai2dev,
+					      uint8_t state)
+{
+	struct sk_buff *skb = ai2_compose_frame(true, AI2_CMD_RECEIVER_STATE,
+						&state, 1);
+	if (!skb)
+		return -ENOMEM;
+
+	return ai2_send_frame(ai2dev, skb);
+}
+
+static int ai2_config_nmea_reports(struct ai2_device *ai2dev,
+				   uint8_t mask)
+{
+	u8 buf[4] = {0};
+	struct sk_buff *skb;
+
+	buf[0] = mask;
+	skb = ai2_compose_frame(true, AI2_CMD_CONFIG_NMEA,
+				buf, sizeof(buf));
+	if (!skb)
+		return -ENOMEM;
+
+	return ai2_send_frame(ai2dev, skb);
+}
+
+/*
+ * Unknown commands, give some version information, must be sent
+ * once, not sure what undoes them besides resetting the whole
+ * bt part, but no sings of significant things being still
+ * turned on without undoing this.
+ */
+static int gnss_ai2_init(struct ai2_device *ai2dev)
+{
+	int ret;
+	u8 d = 0x01;
+	struct sk_buff *skb = ai2_compose_frame(true, 0xf5, &d, 1);
+
+	if (!skb)
+		return -ENOMEM;
+
+	ret = ai2_send_frame(ai2dev, skb);
+	if (ret)
+		return ret;
+
+	msleep(200);
+	d = 5;
+	skb = ai2_compose_frame(true, 0xf1, &d, 1);
+	if (!skb)
+		return -ENOMEM;
+
+	return ai2_send_frame(ai2dev, skb);
+}
+
+static int gnss_ai2_open(struct gnss_device *gdev)
+{
+	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
+	int ret;
+
+	mutex_lock(&ai2dev->gdev_mutex);
+	ai2dev->gdev_open = true;
+	mutex_unlock(&ai2dev->gdev_mutex);
+	if (ai2raw)
+		return 0;
+
+	ret = gnss_ai2_init(ai2dev);
+	if (ret)
+		goto err;
+
+	/* TODO: find out on what kind of ack we should wait */
+	msleep(200);
+	ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
+	if (ret)
+		goto err;
+
+	msleep(200);
+	ret = ai2_config_nmea_reports(ai2dev, NMEA_MASK_ALL);
+	if (ret)
+		goto err;
+
+	msleep(200);
+	ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_ON);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	mutex_lock(&ai2dev->gdev_mutex);
+	ai2dev->gdev_open = false;
+	if (ai2dev->recv_skb)
+		kfree_skb(ai2dev->recv_skb);
+
+	ai2dev->recv_skb = NULL;
+	mutex_unlock(&ai2dev->gdev_mutex);
+	return ret;
+}
+
+static void gnss_ai2_close(struct gnss_device *gdev)
+{
+	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
+
+	/* TODO: find out on what kind of ack we should wait */
+	if (!ai2raw) {
+		msleep(200);
+		ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
+		msleep(200);
+		ai2_set_receiver_state(ai2dev, RECEIVER_STATE_OFF);
+		msleep(200);
+	}
+
+	mutex_lock(&ai2dev->gdev_mutex);
+	ai2dev->gdev_open = false;
+	if (ai2dev->recv_skb)
+		kfree_skb(ai2dev->recv_skb);
+
+	ai2dev->recv_skb = NULL;
+	mutex_unlock(&ai2dev->gdev_mutex);
+}
+
+
+static int gnss_ai2_write_raw(struct gnss_device *gdev,
+		const unsigned char *buf, size_t count)
+{
+	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
+	int err = 0;
+	struct sk_buff *skb = NULL;
+
+	if (!ai2raw)
+		return -EPERM;
+
+	/* allocate packet */
+	skb = ai2_skb_alloc(count, GFP_KERNEL);
+	if (!skb) {
+		BT_ERR("cannot allocate memory for HCILL packet");
+		err = -ENOMEM;
+		goto out;
+	}
+
+	skb_put_data(skb, buf, count);
+
+	err = ai2_send_frame(ai2dev, skb);
+	if (err)
+		goto out;
+
+	return count;
+out:
+	return err;
+}
+
+static const struct gnss_operations gnss_ai2_ops = {
+	.open		= gnss_ai2_open,
+	.close		= gnss_ai2_close,
+	.write_raw	= gnss_ai2_write_raw,
+};
+
+static void process_ai2_packet(struct ai2_device *ai2dev,
+			       u8 cmd, u8 *data, u16 len)
+{
+	if (cmd != AI2_REPORT_NMEA)
+		return;
+
+	if (len <= NMEA_HEADER_LEN)
+		return;
+
+	len -= NMEA_HEADER_LEN;
+	data += NMEA_HEADER_LEN;
+
+	gnss_insert_raw(ai2dev->gdev, data, len);
+}
+
+/* do some sanity checks and split frame into packets */
+static void process_ai2_frame(struct ai2_device *ai2dev)
+{
+	u16 sum;
+	int i;
+	u8 *head;
+	u8 *data;
+
+	sum = 0;
+	data = ai2dev->recv_skb->data;
+	for (i = 0; i < ai2dev->recv_skb->len - 2; i++)
+		sum += data[i];
+
+	print_hex_dump_bytes("ai2 frame: ", DUMP_PREFIX_OFFSET, data, ai2dev->recv_skb->len);
+
+	if (get_unaligned_le16(data + i) != sum) {
+		dev_dbg(ai2dev->dev,
+			"checksum error in reception, dropping frame\n");
+		return;
+	}
+
+	/* reached if byte 1 in the command packet is set to 1 */
+	if (data[1] == AI2_ACK)
+		return;
+
+	head = skb_pull(ai2dev->recv_skb, 2); /* drop frame start marker */
+	while (head && (ai2dev->recv_skb->len >= 3)) {
+		u8 cmd;
+		u16 pktlen;
+
+		cmd = head[0];
+		pktlen = get_unaligned_le16(head + 1);
+		data = skb_pull(ai2dev->recv_skb, 3);
+		if (!data)
+			break;
+
+		if (pktlen > ai2dev->recv_skb->len)
+			break;
+
+		head = skb_pull(ai2dev->recv_skb, pktlen);
+
+		process_ai2_packet(ai2dev, cmd, data, pktlen);
+	}
+}
+
+static void process_ai2_data(struct ai2_device *ai2dev,
+			     u8 *data, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (!ai2dev->recv_skb) {
+			ai2dev->recv_esc = false;
+			if (data[i] != AI2_ESCAPE) {
+				dev_dbg(ai2dev->dev, "dropping data, trying to resync\n");
+				continue;
+			}
+			ai2dev->recv_skb = alloc_skb(MAX_AI2_FRAME_SIZE, GFP_KERNEL);
+			if (!ai2dev->recv_skb)
+				return;
+
+			dev_dbg(ai2dev->dev, "starting packet\n");
+
+			/* this initial AI2_ESCAPE is part of checksum computation */
+			skb_put_u8(ai2dev->recv_skb, data[i]);
+			continue;
+		}
+		if (ai2dev->recv_skb->len == 1) {
+			if (data[i] == AI2_END_MARKER) {
+				dev_dbg(ai2dev->dev, "unexpected end of frame received\n");
+				kfree_skb(ai2dev->recv_skb);
+				ai2dev->recv_skb = NULL;
+				continue;
+			}
+			skb_put_u8(ai2dev->recv_skb, data[i]);
+		} else {
+			/* drop one of two AI2_ESCAPE */
+			if ((!ai2dev->recv_esc) &&
+			   (data[i] == AI2_ESCAPE)) {
+				ai2dev->recv_esc = true;
+				continue;
+			}
+
+			if (ai2dev->recv_esc &&
+			    (data[i] == AI2_END_MARKER)) {
+				process_ai2_frame(ai2dev);
+				kfree_skb(ai2dev->recv_skb);
+				ai2dev->recv_skb = NULL;
+				continue;
+			}
+			skb_put_u8(ai2dev->recv_skb, data[i]);
+		}
+	}
+}
+
+static void gnss_recv_frame(struct device *dev, struct sk_buff *skb)
+{
+	struct ai2_device *ai2dev = dev_get_drvdata(dev);
+	struct gps_event_hdr *gnss_hdr;
+	u8 *data;
+
+	if (!ai2dev->gdev) {
+		kfree_skb(skb);
+		return;
+	}
+
+	gnss_hdr = (struct gps_event_hdr *)skb->data;
+
+	data = skb_pull(skb, sizeof(*gnss_hdr));
+	/*
+	 * REVISIT: maybe do something with the completed
+	 * event
+	 */
+	if (gnss_hdr->opcode ==	GPS_CH9_OP_READ) {
+		mutex_lock(&ai2dev->gdev_mutex);
+		if (ai2dev->gdev_open) {
+			if (ai2raw)
+				gnss_insert_raw(ai2dev->gdev, data, skb->len);
+			else
+				process_ai2_data(ai2dev, data, skb->len);
+		} else {
+			dev_dbg(ai2dev->dev,
+				"receiving data while chip should be off\n");
+		}
+		mutex_unlock(&ai2dev->gdev_mutex);
+	}
+	kfree_skb(skb);
+}
+
+static int gnss_ai2_probe(struct platform_device *pdev)
+{
+	struct gnss_device *gdev;
+	struct ai2_device *ai2dev;
+	int ret;
+
+	ai2dev = devm_kzalloc(&pdev->dev, sizeof(*ai2dev), GFP_KERNEL);
+	if (!ai2dev)
+		return -ENOMEM;
+
+	ai2dev->dev = &pdev->dev;
+	gdev = gnss_allocate_device(&pdev->dev);
+	if (!gdev)
+		return -ENOMEM;
+
+	gdev->ops = &gnss_ai2_ops;
+	gdev->type = ai2raw ? GNSS_TYPE_AI2 : GNSS_TYPE_NMEA;
+	gnss_set_drvdata(gdev, ai2dev);
+	platform_set_drvdata(pdev, ai2dev);
+	st_set_gnss_recv_func(pdev->dev.parent, gnss_recv_frame);
+	mutex_init(&ai2dev->gdev_mutex);
+
+	ret = gnss_register_device(gdev);
+	if (ret)
+		goto err;
+
+	ai2dev->gdev = gdev;
+	return 0;
+
+err:
+	st_set_gnss_recv_func(pdev->dev.parent, NULL);
+
+	if (ai2dev->recv_skb)
+		kfree_skb(ai2dev->recv_skb);
+
+	gnss_put_device(gdev);
+	return ret;
+}
+
+static void gnss_ai2_remove(struct platform_device *pdev)
+{
+	struct ai2_device *ai2dev =  platform_get_drvdata(pdev);
+
+	st_set_gnss_recv_func(pdev->dev.parent, NULL);
+	gnss_deregister_device(ai2dev->gdev);
+	gnss_put_device(ai2dev->gdev);
+	if (ai2dev->recv_skb)
+		kfree_skb(ai2dev->recv_skb);
+}
+
+static const struct platform_device_id gnss_ai2_id[] = {
+	{
+		.name = "ti-ai2-gnss"
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(platform, gnss_ai2_id);
+
+static struct platform_driver gnss_ai2_driver = {
+	.driver = {
+		.name = "gnss-ai2",
+	},
+	.probe		= gnss_ai2_probe,
+	.remove_new	= gnss_ai2_remove,
+	.id_table	= gnss_ai2_id,
+};
+module_platform_driver(gnss_ai2_driver);
+
+module_param(ai2raw, bool, 0600);
+MODULE_DESCRIPTION("AI2 GNSS driver");
+MODULE_LICENSE("GPL");
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips
  2024-01-28 17:33 ` [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips Andreas Kemnade
@ 2024-01-29  7:40   ` Paul Menzel
  0 siblings, 0 replies; 12+ messages in thread
From: Paul Menzel @ 2024-01-29  7:40 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Dear Andreas,


Am 28.01.24 um 18:33 schrieb Andreas Kemnade:
> Some of these chips have GNSS support. GNSS support
> is available through channel 9 whilst FM is through channel 8.
> Add a platform subdevice for GNSS so that a driver for that
> functionality can be build. To avoid having useless GNSS

be buil*t*

> devices, do it only when the devicetree node namecontains gnss.

… name contains …

This could be re-flowed for 75 characters per line.

Also, I believe, you should capitalize the commit message summary prefix 
Bluetooth.

An what device were you able to test this?

> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>   drivers/bluetooth/hci_ll.c   | 81 ++++++++++++++++++++++++++++++++++++
>   include/linux/ti_wilink_st.h |  8 ++++
>   2 files changed, 89 insertions(+)
> 
> diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c
> index 4a0b5c3160c2b..09e5a4dbd2f8c 100644
> --- a/drivers/bluetooth/hci_ll.c
> +++ b/drivers/bluetooth/hci_ll.c
> @@ -32,6 +32,7 @@
>   #include <linux/signal.h>
>   #include <linux/ioctl.h>
>   #include <linux/of.h>
> +#include <linux/platform_device.h>
>   #include <linux/serdev.h>
>   #include <linux/skbuff.h>
>   #include <linux/ti_wilink_st.h>
> @@ -68,6 +69,9 @@ struct ll_device {
>   	struct gpio_desc *enable_gpio;
>   	struct clk *ext_clk;
>   	bdaddr_t bdaddr;
> +
> +	void (*gnss_recv_func)(struct device *dev, struct sk_buff *skb);
> +	struct platform_device *gnssdev;
>   };
>   
>   struct ll_struct {
> @@ -78,6 +82,8 @@ struct ll_struct {
>   	struct sk_buff_head tx_wait_q;	/* HCILL wait queue	*/
>   };
>   
> +static int ll_gnss_register(struct ll_device *lldev);
> +static int ll_gnss_recv_frame(struct hci_dev *hdev, struct sk_buff *skb);
>   /*
>    * Builds and sends an HCILL command packet.
>    * These are very simple packets with only 1 cmd byte
> @@ -411,6 +417,13 @@ static int ll_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
>   	.lsize = 0, \
>   	.maxlen = 0
>   
> +#define LL_RECV_GNSS \
> +	.type = 9, \
> +	.hlen = 3, \
> +	.loff = 1, \
> +	.lsize = 2
> +
> +
>   static const struct h4_recv_pkt ll_recv_pkts[] = {
>   	{ H4_RECV_ACL,       .recv = hci_recv_frame },
>   	{ H4_RECV_SCO,       .recv = hci_recv_frame },
> @@ -419,6 +432,7 @@ static const struct h4_recv_pkt ll_recv_pkts[] = {
>   	{ LL_RECV_SLEEP_ACK, .recv = ll_recv_frame  },
>   	{ LL_RECV_WAKE_IND,  .recv = ll_recv_frame  },
>   	{ LL_RECV_WAKE_ACK,  .recv = ll_recv_frame  },
> +	{ LL_RECV_GNSS,      .recv = ll_gnss_recv_frame },
>   };
>   
>   /* Recv data */
> @@ -677,9 +691,69 @@ static int ll_setup(struct hci_uart *hu)
>   		}
>   	}
>   
> +	if (strstr(of_node_full_name(serdev->dev.of_node), "gnss"))
> +		ll_gnss_register(lldev);
> +
> +	return 0;
> +}
> +
> +struct hci_dev *st_get_hci(struct device *dev)
> +{
> +	struct ll_device *lldev = dev_get_drvdata(dev);
> +
> +	return lldev->hu.hdev;
> +}
> +EXPORT_SYMBOL(st_get_hci);
> +
> +void st_set_gnss_recv_func(struct device *dev,
> +			   void (*recv_frame)(struct device *, struct sk_buff *))
> +{
> +	struct ll_device *lldev = dev_get_drvdata(dev);
> +
> +	lldev->gnss_recv_func = recv_frame;
> +}
> +EXPORT_SYMBOL(st_set_gnss_recv_func);
> +
> +static int ll_gnss_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +	struct hci_uart *hu = hci_get_drvdata(hdev);
> +	struct ll_device *lldev = container_of(hu, struct ll_device, hu);
> +
> +	if (!lldev->gnssdev)
> +		return 0;
> +
> +	if (lldev->gnss_recv_func) {
> +		lldev->gnss_recv_func(&lldev->gnssdev->dev, skb);
> +		return 0;
> +	}
> +	kfree_skb(skb);
> +
>   	return 0;
>   }
>   
> +static int ll_gnss_register(struct ll_device *lldev)
> +{
> +	struct platform_device *pdev;
> +	int ret;
> +
> +	pdev = platform_device_alloc("ti-ai2-gnss", PLATFORM_DEVID_AUTO);
> +	if (!pdev)
> +		return -ENOMEM;
> +
> +	pdev->dev.parent = &lldev->serdev->dev;
> +	lldev->gnssdev = pdev;
> +	ret = platform_device_add(pdev);
> +	if (ret)
> +		goto err;
> +
> +	return 0;
> +
> +err:
> +	lldev->gnssdev = NULL;
> +	platform_device_put(pdev);
> +	return ret;
> +}
> +
>   static const struct hci_uart_proto llp;
>   
>   static int hci_ti_probe(struct serdev_device *serdev)
> @@ -757,12 +831,19 @@ static int hci_ti_probe(struct serdev_device *serdev)
>   	}
>   
>   	return hci_uart_register_device(hu, &llp);
> +
> +
> +	return 0;
>   }
>   
> +
>   static void hci_ti_remove(struct serdev_device *serdev)
>   {
>   	struct ll_device *lldev = serdev_device_get_drvdata(serdev);
>   
> +	if (lldev->gnssdev)
> +		platform_device_unregister(lldev->gnssdev);
> +
>   	hci_uart_unregister_device(&lldev->hu);
>   }
>   
> diff --git a/include/linux/ti_wilink_st.h b/include/linux/ti_wilink_st.h
> index 10642d4844f0c..eccc2db004069 100644
> --- a/include/linux/ti_wilink_st.h
> +++ b/include/linux/ti_wilink_st.h
> @@ -381,6 +381,14 @@ unsigned long st_ll_getstate(struct st_data_s *);
>   unsigned long st_ll_sleep_state(struct st_data_s *, unsigned char);
>   void st_ll_wakeup(struct st_data_s *);
>   
> +/**
> + * various funcs used to interact between FM, GPS and BT
> + */
> +struct hci_dev *st_get_hci(struct device *dev);
> +void st_set_gnss_recv_func(struct device *dev,
> +			   void (*recv_frame)(struct device *, struct sk_buff *));
> +
> +
>   /*
>    * header information used by st_core.c for FM and GPS
>    * packet parsing, the bluetooth headers are already available

Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>


Kind regards,

Paul

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips.
  2024-01-28 17:33 ` [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips Andreas Kemnade
@ 2024-01-29  7:40   ` Paul Menzel
  2024-01-29 20:34     ` Andreas Kemnade
  0 siblings, 1 reply; 12+ messages in thread
From: Paul Menzel @ 2024-01-29  7:40 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Dear Andreas,


Thank you for your patch. I think Linux Bluetooth patches need to be 
have Bluetooth as the prefix for the commit message summary/title. Also, 
it’d be great if you removed the dot/period at the end of the commit 
message summary/title.

Am 28.01.24 um 18:33 schrieb Andreas Kemnade:
> Texas Instruments uses something called Air Independent Interface (AI2) for
> their WLAN/BT/GPS combo chips.
> No public documentation is available, but allow that protocol to be
> specified.

I’d add a blank line between paragraphs.

> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>   drivers/gnss/core.c  | 1 +
>   include/linux/gnss.h | 1 +
>   2 files changed, 2 insertions(+)
> 
> diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c
> index 48f2ee0f78c4d..cac9f45aec4b2 100644
> --- a/drivers/gnss/core.c
> +++ b/drivers/gnss/core.c
> @@ -335,6 +335,7 @@ static const char * const gnss_type_names[GNSS_TYPE_COUNT] = {
>   	[GNSS_TYPE_SIRF]	= "SiRF",
>   	[GNSS_TYPE_UBX]		= "UBX",
>   	[GNSS_TYPE_MTK]		= "MTK",
> +	[GNSS_TYPE_AI2]		= "AI2",
>   };
>   
>   static const char *gnss_type_name(const struct gnss_device *gdev)
> diff --git a/include/linux/gnss.h b/include/linux/gnss.h
> index 36968a0f33e8d..16b565dab83ea 100644
> --- a/include/linux/gnss.h
> +++ b/include/linux/gnss.h
> @@ -23,6 +23,7 @@ enum gnss_type {
>   	GNSS_TYPE_SIRF,
>   	GNSS_TYPE_UBX,
>   	GNSS_TYPE_MTK,
> +	GNSS_TYPE_AI2,
>   
>   	GNSS_TYPE_COUNT
>   };

Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>


Kind regards,

Paul

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-01-28 17:33 ` [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Andreas Kemnade
@ 2024-01-29  7:53   ` Paul Menzel
  2024-01-29 20:53     ` Andreas Kemnade
  2024-02-25 22:16     ` Andreas Kemnade
  2024-02-13 13:57   ` Adam Ford
  1 sibling, 2 replies; 12+ messages in thread
From: Paul Menzel @ 2024-01-29  7:53 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Dear Andreas,


Thank you for your patch.


Am 28.01.24 um 18:33 schrieb Andreas Kemnade:
> Add a driver for the Air Independent Interface protocol used by some TI
> Wilink combo chips. Per default, send out just NMEA to userspace and turn
> on/off things at open()/close() but keep the door open for any
> sophisticated development regarding the AI2 protocol by having a kernel
> parameter to turn it into raw mode resembling /dev/tigps provided by some
> TI vendor kernels.

It’d be great, if you could add the name the kernel parameter *ai2raw*, 
and also document at least one of these vendor kernels.

Could you give a high level overview of the driver design?

What device did you test with?

> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>   drivers/gnss/Kconfig  |  13 ++
>   drivers/gnss/Makefile |   3 +
>   drivers/gnss/ai2.c    | 523 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 539 insertions(+)
>   create mode 100644 drivers/gnss/ai2.c
> 
> diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
> index d7fe265c28696..3a20212dacc9e 100644
> --- a/drivers/gnss/Kconfig
> +++ b/drivers/gnss/Kconfig
> @@ -65,4 +65,17 @@ config GNSS_USB
>   
>   	  If unsure, say N.
>   
> +config GNSS_AI2
> +	tristate "TI AI2 procotol support"
> +	depends on BT_HCIUART_LL
> +	help
> +	  Say Y here if you have a Texas Instruments Wilink combo chip
> +	  contaning among other things a GNSS receiver speaking the

conta*i*ning

> +	  Air Independent Interface (AI2) protocol.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called gnss-ai2.
> +
> +	  If unsure, say N.
> +
>   endif # GNSS
> diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
> index bb2cbada34359..bf6fefcb2e823 100644
> --- a/drivers/gnss/Makefile
> +++ b/drivers/gnss/Makefile
> @@ -20,3 +20,6 @@ gnss-ubx-y := ubx.o
>   
>   obj-$(CONFIG_GNSS_USB)			+= gnss-usb.o
>   gnss-usb-y := usb.o
> +
> +obj-$(CONFIG_GNSS_AI2)			+= gnss-ai2.o
> +gnss-ai2-y := ai2.o
> diff --git a/drivers/gnss/ai2.c b/drivers/gnss/ai2.c
> new file mode 100644
> index 0000000000000..673fbe8de7ef2
> --- /dev/null
> +++ b/drivers/gnss/ai2.c
> @@ -0,0 +1,523 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Texas Instruments AI2 (Air independent interface) protocol device driver
> + * Used for some TI WLAN/Bluetooth/GNSS combo chips.
> + *
> + * Copyright (C) 2024 Andreas Kemnade <andreas@kemnade.info>
> + */
> +#include <linux/errno.h>
> +#include <linux/gnss.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/ti_wilink_st.h>
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +
> +/* Channel-9 details for GPS */
> +#define GPS_CH9_PKT_NUMBER		0x9
> +#define GPS_CH9_OP_WRITE		0x1
> +#define GPS_CH9_OP_READ			0x2
> +#define GPS_CH9_OP_COMPLETED_EVT	0x3
> +
> +/* arbitarily chosen, should fit everything seen in the past */
> +#define MAX_AI2_FRAME_SIZE 2048
> +
> +#define AI2_ESCAPE 0x10 /* if sent as data, it is doubled */
> +#define AI2_END_MARKER 0x3
> +#define AI2_ACK 0x2
> +
> +/* reports */
> +#define AI2_REPORT_NMEA 0xd3
> +
> +#define NMEA_HEADER_LEN 4
> +
> +/* commands */
> +#define AI2_CMD_RECEIVER_STATE 2
> +
> +#define RECEIVER_STATE_OFF 1
> +#define RECEIVER_STATE_IDLE 2
> +#define RECEIVER_STATE_ON 3
> +
> +#define AI2_CMD_CONFIG_NMEA 0xe5
> +#define NMEA_MASK_GGA (1 << 0)
> +#define NMEA_MASK_GLL (1 << 1)
> +#define NMEA_MASK_GSA (1 << 2)
> +#define NMEA_MASK_GSV (1 << 3)
> +#define NMEA_MASK_RMC (1 << 4)
> +#define NMEA_MASK_VTG (1 << 5)
> +
> +#define NMEA_MASK_ALL (NMEA_MASK_GGA | \
> +		NMEA_MASK_GLL | \
> +		NMEA_MASK_GSA | \
> +		NMEA_MASK_GSV | \
> +		NMEA_MASK_RMC | \
> +		NMEA_MASK_VTG)
> +
> +
> +static bool ai2raw;
> +
> +struct ai2_device {
> +	struct mutex gdev_mutex;
> +	bool gdev_open;
> +	struct gnss_device *gdev;
> +	struct device *dev;
> +	struct sk_buff *recv_skb;
> +	bool recv_esc;
> +};
> +
> +static struct sk_buff *ai2_skb_alloc(unsigned int len, gfp_t how)
> +{
> +	struct sk_buff *skb;
> +
> +	skb = bt_skb_alloc(len + sizeof(struct gps_event_hdr), how);
> +	if (skb)
> +		skb_reserve(skb, sizeof(struct gps_event_hdr));
> +
> +	return skb;
> +}
> +
> +static int ai2_send_frame(struct ai2_device *ai2dev,
> +			  struct sk_buff *skb)
> +{
> +	int len;
> +	struct gps_event_hdr *gnssdrv_hdr;
> +	struct hci_dev *hdev;
> +
> +	if (skb->len >= U16_MAX)
> +		return -EINVAL;
> +
> +	/*
> +	 * note: fragmentation at this point not handled yet
> +	 * not needed for simple config commands
> +	 */
> +	len = skb->len;
> +	gnssdrv_hdr = skb_push(skb, sizeof(struct gps_event_hdr));
> +	gnssdrv_hdr->opcode = GPS_CH9_OP_WRITE;
> +	gnssdrv_hdr->plen = __cpu_to_le16(len);
> +
> +	hci_skb_pkt_type(skb) = GPS_CH9_PKT_NUMBER;
> +	hdev = st_get_hci(ai2dev->dev->parent);
> +	return hdev->send(hdev, skb);
> +}
> +
> +static void ai2_put_escaped(struct sk_buff *skb, u8 d)
> +{
> +	skb_put_u8(skb, d);
> +	if (d == 0x10)
> +		skb_put_u8(skb, d);
> +}
> +
> +static struct sk_buff *ai2_compose_frame(bool request_ack,
> +					u8 cmd,
> +					const u8 *data,
> +					int len)
> +{
> +	u16 sum;

I’d use a non-fixed type.

> +	int i;
> +	/* duplicate the length to have space for worst case escaping */
> +	struct sk_buff *skb = ai2_skb_alloc(2 + len * 2 + 2 + 2, GFP_KERNEL);
> +
> +	skb_put_u8(skb, AI2_ESCAPE);
> +	skb_put_u8(skb, request_ack ? 1 : 0);
> +
> +	sum = AI2_ESCAPE;
> +	if (request_ack)
> +		sum++;
> +
> +	ai2_put_escaped(skb, cmd);
> +	sum += cmd;
> +
> +	ai2_put_escaped(skb, len & 0xff);
> +	sum += len & 0xff;
> +
> +	ai2_put_escaped(skb, len >> 8);
> +	sum += len >> 8;
> +
> +	for (i = 0; i < len; i++) {
> +		sum += data[i];
> +		ai2_put_escaped(skb, data[i]);
> +	}
> +
> +	ai2_put_escaped(skb, sum & 0xFF);
> +	ai2_put_escaped(skb, sum >> 8);
> +	skb_put_u8(skb, AI2_ESCAPE);
> +	skb_put_u8(skb, AI2_END_MARKER);
> +
> +	return skb;
> +}
> +
> +static int ai2_set_receiver_state(struct ai2_device *ai2dev,
> +					      uint8_t state)
> +{
> +	struct sk_buff *skb = ai2_compose_frame(true, AI2_CMD_RECEIVER_STATE,
> +						&state, 1);
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	return ai2_send_frame(ai2dev, skb);
> +}
> +
> +static int ai2_config_nmea_reports(struct ai2_device *ai2dev,
> +				   uint8_t mask)
> +{
> +	u8 buf[4] = {0};
> +	struct sk_buff *skb;
> +
> +	buf[0] = mask;
> +	skb = ai2_compose_frame(true, AI2_CMD_CONFIG_NMEA,
> +				buf, sizeof(buf));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	return ai2_send_frame(ai2dev, skb);
> +}
> +
> +/*
> + * Unknown commands, give some version information, must be sent
> + * once, not sure what undoes them besides resetting the whole
> + * bt part, but no sings of significant things being still

si*gn*s

> + * turned on without undoing this.
> + */
> +static int gnss_ai2_init(struct ai2_device *ai2dev)
> +{
> +	int ret;
> +	u8 d = 0x01;
> +	struct sk_buff *skb = ai2_compose_frame(true, 0xf5, &d, 1);
> +
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	ret = ai2_send_frame(ai2dev, skb);
> +	if (ret)
> +		return ret;
> +
> +	msleep(200);

I’d be great if you added a comment, why this long delay is needed.

> +	d = 5;
> +	skb = ai2_compose_frame(true, 0xf1, &d, 1);
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	return ai2_send_frame(ai2dev, skb);
> +}
> +
> +static int gnss_ai2_open(struct gnss_device *gdev)
> +{
> +	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +	int ret;
> +
> +	mutex_lock(&ai2dev->gdev_mutex);
> +	ai2dev->gdev_open = true;
> +	mutex_unlock(&ai2dev->gdev_mutex);
> +	if (ai2raw)
> +		return 0;
> +
> +	ret = gnss_ai2_init(ai2dev);
> +	if (ret)
> +		goto err;
> +
> +	/* TODO: find out on what kind of ack we should wait */
> +	msleep(200);
> +	ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
> +	if (ret)
> +		goto err;
> +
> +	msleep(200);
> +	ret = ai2_config_nmea_reports(ai2dev, NMEA_MASK_ALL);
> +	if (ret)
> +		goto err;
> +
> +	msleep(200);
> +	ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_ON);
> +	if (ret)
> +		goto err;
> +
> +	return 0;
> +err:
> +	mutex_lock(&ai2dev->gdev_mutex);
> +	ai2dev->gdev_open = false;
> +	if (ai2dev->recv_skb)
> +		kfree_skb(ai2dev->recv_skb);
> +
> +	ai2dev->recv_skb = NULL;
> +	mutex_unlock(&ai2dev->gdev_mutex);
> +	return ret;
> +}
> +
> +static void gnss_ai2_close(struct gnss_device *gdev)
> +{
> +	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +
> +	/* TODO: find out on what kind of ack we should wait */
> +	if (!ai2raw) {
> +		msleep(200);
> +		ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
> +		msleep(200);
> +		ai2_set_receiver_state(ai2dev, RECEIVER_STATE_OFF);
> +		msleep(200);
> +	}
> +
> +	mutex_lock(&ai2dev->gdev_mutex);
> +	ai2dev->gdev_open = false;
> +	if (ai2dev->recv_skb)
> +		kfree_skb(ai2dev->recv_skb);
> +
> +	ai2dev->recv_skb = NULL;
> +	mutex_unlock(&ai2dev->gdev_mutex);
> +}
> +
> +
> +static int gnss_ai2_write_raw(struct gnss_device *gdev,
> +		const unsigned char *buf, size_t count)
> +{
> +	struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +	int err = 0;
> +	struct sk_buff *skb = NULL;
> +
> +	if (!ai2raw)
> +		return -EPERM;
> +
> +	/* allocate packet */
> +	skb = ai2_skb_alloc(count, GFP_KERNEL);
> +	if (!skb) {
> +		BT_ERR("cannot allocate memory for HCILL packet");
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	skb_put_data(skb, buf, count);
> +
> +	err = ai2_send_frame(ai2dev, skb);
> +	if (err)
> +		goto out;
> +
> +	return count;
> +out:
> +	return err;
> +}
> +
> +static const struct gnss_operations gnss_ai2_ops = {
> +	.open		= gnss_ai2_open,
> +	.close		= gnss_ai2_close,
> +	.write_raw	= gnss_ai2_write_raw,
> +};
> +
> +static void process_ai2_packet(struct ai2_device *ai2dev,
> +			       u8 cmd, u8 *data, u16 len)
> +{
> +	if (cmd != AI2_REPORT_NMEA)
> +		return;
> +
> +	if (len <= NMEA_HEADER_LEN)
> +		return;
> +
> +	len -= NMEA_HEADER_LEN;
> +	data += NMEA_HEADER_LEN;
> +
> +	gnss_insert_raw(ai2dev->gdev, data, len);
> +}
> +
> +/* do some sanity checks and split frame into packets */
> +static void process_ai2_frame(struct ai2_device *ai2dev)
> +{
> +	u16 sum;
> +	int i;
> +	u8 *head;
> +	u8 *data;
> +
> +	sum = 0;
> +	data = ai2dev->recv_skb->data;
> +	for (i = 0; i < ai2dev->recv_skb->len - 2; i++)
> +		sum += data[i];
> +
> +	print_hex_dump_bytes("ai2 frame: ", DUMP_PREFIX_OFFSET, data, ai2dev->recv_skb->len);
> +
> +	if (get_unaligned_le16(data + i) != sum) {
> +		dev_dbg(ai2dev->dev,
> +			"checksum error in reception, dropping frame\n");
> +		return;
> +	}
> +
> +	/* reached if byte 1 in the command packet is set to 1 */
> +	if (data[1] == AI2_ACK)
> +		return;
> +
> +	head = skb_pull(ai2dev->recv_skb, 2); /* drop frame start marker */
> +	while (head && (ai2dev->recv_skb->len >= 3)) {
> +		u8 cmd;
> +		u16 pktlen;
> +
> +		cmd = head[0];
> +		pktlen = get_unaligned_le16(head + 1);
> +		data = skb_pull(ai2dev->recv_skb, 3);
> +		if (!data)
> +			break;
> +
> +		if (pktlen > ai2dev->recv_skb->len)
> +			break;
> +
> +		head = skb_pull(ai2dev->recv_skb, pktlen);
> +
> +		process_ai2_packet(ai2dev, cmd, data, pktlen);
> +	}
> +}
> +
> +static void process_ai2_data(struct ai2_device *ai2dev,
> +			     u8 *data, int len)
> +{
> +	int i;
> +
> +	for (i = 0; i < len; i++) {
> +		if (!ai2dev->recv_skb) {
> +			ai2dev->recv_esc = false;
> +			if (data[i] != AI2_ESCAPE) {
> +				dev_dbg(ai2dev->dev, "dropping data, trying to resync\n");
> +				continue;
> +			}
> +			ai2dev->recv_skb = alloc_skb(MAX_AI2_FRAME_SIZE, GFP_KERNEL);
> +			if (!ai2dev->recv_skb)
> +				return;
> +
> +			dev_dbg(ai2dev->dev, "starting packet\n");
> +
> +			/* this initial AI2_ESCAPE is part of checksum computation */
> +			skb_put_u8(ai2dev->recv_skb, data[i]);
> +			continue;
> +		}
> +		if (ai2dev->recv_skb->len == 1) {
> +			if (data[i] == AI2_END_MARKER) {
> +				dev_dbg(ai2dev->dev, "unexpected end of frame received\n");
> +				kfree_skb(ai2dev->recv_skb);
> +				ai2dev->recv_skb = NULL;
> +				continue;
> +			}
> +			skb_put_u8(ai2dev->recv_skb, data[i]);
> +		} else {
> +			/* drop one of two AI2_ESCAPE */
> +			if ((!ai2dev->recv_esc) &&
> +			   (data[i] == AI2_ESCAPE)) {
> +				ai2dev->recv_esc = true;
> +				continue;
> +			}
> +
> +			if (ai2dev->recv_esc &&
> +			    (data[i] == AI2_END_MARKER)) {
> +				process_ai2_frame(ai2dev);
> +				kfree_skb(ai2dev->recv_skb);
> +				ai2dev->recv_skb = NULL;
> +				continue;
> +			}
> +			skb_put_u8(ai2dev->recv_skb, data[i]);
> +		}
> +	}
> +}
> +
> +static void gnss_recv_frame(struct device *dev, struct sk_buff *skb)
> +{
> +	struct ai2_device *ai2dev = dev_get_drvdata(dev);
> +	struct gps_event_hdr *gnss_hdr;
> +	u8 *data;
> +
> +	if (!ai2dev->gdev) {
> +		kfree_skb(skb);
> +		return;
> +	}
> +
> +	gnss_hdr = (struct gps_event_hdr *)skb->data;
> +
> +	data = skb_pull(skb, sizeof(*gnss_hdr));
> +	/*
> +	 * REVISIT: maybe do something with the completed
> +	 * event
> +	 */
> +	if (gnss_hdr->opcode ==	GPS_CH9_OP_READ) {
> +		mutex_lock(&ai2dev->gdev_mutex);
> +		if (ai2dev->gdev_open) {
> +			if (ai2raw)
> +				gnss_insert_raw(ai2dev->gdev, data, skb->len);
> +			else
> +				process_ai2_data(ai2dev, data, skb->len);
> +		} else {
> +			dev_dbg(ai2dev->dev,
> +				"receiving data while chip should be off\n");
> +		}
> +		mutex_unlock(&ai2dev->gdev_mutex);
> +	}
> +	kfree_skb(skb);
> +}
> +
> +static int gnss_ai2_probe(struct platform_device *pdev)
> +{
> +	struct gnss_device *gdev;
> +	struct ai2_device *ai2dev;
> +	int ret;
> +
> +	ai2dev = devm_kzalloc(&pdev->dev, sizeof(*ai2dev), GFP_KERNEL);
> +	if (!ai2dev)
> +		return -ENOMEM;
> +
> +	ai2dev->dev = &pdev->dev;
> +	gdev = gnss_allocate_device(&pdev->dev);
> +	if (!gdev)
> +		return -ENOMEM;
> +
> +	gdev->ops = &gnss_ai2_ops;
> +	gdev->type = ai2raw ? GNSS_TYPE_AI2 : GNSS_TYPE_NMEA;
> +	gnss_set_drvdata(gdev, ai2dev);
> +	platform_set_drvdata(pdev, ai2dev);
> +	st_set_gnss_recv_func(pdev->dev.parent, gnss_recv_frame);
> +	mutex_init(&ai2dev->gdev_mutex);
> +
> +	ret = gnss_register_device(gdev);
> +	if (ret)
> +		goto err;
> +
> +	ai2dev->gdev = gdev;
> +	return 0;
> +
> +err:
> +	st_set_gnss_recv_func(pdev->dev.parent, NULL);
> +
> +	if (ai2dev->recv_skb)
> +		kfree_skb(ai2dev->recv_skb);
> +
> +	gnss_put_device(gdev);
> +	return ret;
> +}
> +
> +static void gnss_ai2_remove(struct platform_device *pdev)
> +{
> +	struct ai2_device *ai2dev =  platform_get_drvdata(pdev);
> +
> +	st_set_gnss_recv_func(pdev->dev.parent, NULL);
> +	gnss_deregister_device(ai2dev->gdev);
> +	gnss_put_device(ai2dev->gdev);
> +	if (ai2dev->recv_skb)
> +		kfree_skb(ai2dev->recv_skb);
> +}
> +
> +static const struct platform_device_id gnss_ai2_id[] = {
> +	{
> +		.name = "ti-ai2-gnss"
> +	}, {
> +		/* sentinel */
> +	}
> +};
> +MODULE_DEVICE_TABLE(platform, gnss_ai2_id);
> +
> +static struct platform_driver gnss_ai2_driver = {
> +	.driver = {
> +		.name = "gnss-ai2",
> +	},
> +	.probe		= gnss_ai2_probe,
> +	.remove_new	= gnss_ai2_remove,
> +	.id_table	= gnss_ai2_id,
> +};
> +module_platform_driver(gnss_ai2_driver);
> +
> +module_param(ai2raw, bool, 0600);
> +MODULE_DESCRIPTION("AI2 GNSS driver");
> +MODULE_LICENSE("GPL");

Acked-by: Paul Menzel <pmenzel@molgen.mpg.de>


Kind regards,

Paul

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips.
  2024-01-29  7:40   ` Paul Menzel
@ 2024-01-29 20:34     ` Andreas Kemnade
  0 siblings, 0 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-29 20:34 UTC (permalink / raw)
  To: Paul Menzel
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Hi,

On Mon, 29 Jan 2024 08:40:58 +0100
Paul Menzel <pmenzel@molgen.mpg.de> wrote:

> Dear Andreas,
> 
> 
> Thank you for your patch. I think Linux Bluetooth patches need to be 
> have Bluetooth as the prefix for the commit message summary/title. Also, 
> it’d be great if you removed the dot/period at the end of the commit 
> message summary/title.
> 
well, it is not a Bluetooth patch, although sent in a series with a Bluetooth
patch included, so having Bluetooth as th prefix just does not make any sense.

Regards,
Andreas

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-01-29  7:53   ` Paul Menzel
@ 2024-01-29 20:53     ` Andreas Kemnade
  2024-02-25 22:16     ` Andreas Kemnade
  1 sibling, 0 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-01-29 20:53 UTC (permalink / raw)
  To: Paul Menzel
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

On Mon, 29 Jan 2024 08:53:27 +0100
Paul Menzel <pmenzel@molgen.mpg.de> wrote:

> Dear Andreas,
> 
> 
> Thank you for your patch.
> 
> 
> Am 28.01.24 um 18:33 schrieb Andreas Kemnade:
> > Add a driver for the Air Independent Interface protocol used by some TI
> > Wilink combo chips. Per default, send out just NMEA to userspace and turn
> > on/off things at open()/close() but keep the door open for any
> > sophisticated development regarding the AI2 protocol by having a kernel
> > parameter to turn it into raw mode resembling /dev/tigps provided by some
> > TI vendor kernels.  
> 
> It’d be great, if you could add the name the kernel parameter *ai2raw*, 
> and also document at least one of these vendor kernels.
> 
> Could you give a high level overview of the driver design?
> 
basically I rely on the hci_ll stuff for doing wakeup and initializing
the firmware. So I queue up there another sort of TLV construction besides
the Bluetooth packets using the hci_dev infrastructure and offload all the
GNSS specific handling to a platform subdevice.
So in raw AI2 mode, the input is just en/decapsulated into GPS_CH9_OP_READ/WRITE
packets and sent/recv via the hci queuing system (gnss_recv_frame() / ai2_send_frame()).

On top of that in NMEA mode, proper intialization is done at
open()/close(). The commands are in ai2_compose_frame() prepared by adding
checksums, escaping any occurance of 0x10 and adding start/end markes()
In the rx path, the mechanism works the other way round and if the packet
is an NMEA report it is sent to userspace.

> What device did you test with?
>
As said in 0/3 I am testing with an Epson Moverio BT-200 containing
a WL1283.

Regards,
Andreas

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-01-28 17:33 ` [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Andreas Kemnade
  2024-01-29  7:53   ` Paul Menzel
@ 2024-02-13 13:57   ` Adam Ford
  2024-02-13 19:20     ` Andreas Kemnade
  1 sibling, 1 reply; 12+ messages in thread
From: Adam Ford @ 2024-02-13 13:57 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Tony Lindgren, tomi.valkeinen,
	Péter Ujfalusi, robh, hns

On Sun, Jan 28, 2024 at 11:34 AM Andreas Kemnade <andreas@kemnade.info> wrote:
>
> Add a driver for the Air Independent Interface protocol used by some TI
> Wilink combo chips. Per default, send out just NMEA to userspace and turn
> on/off things at open()/close() but keep the door open for any
> sophisticated development regarding the AI2 protocol by having a kernel
> parameter to turn it into raw mode resembling /dev/tigps provided by some
> TI vendor kernels.
>
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>

I am still trying to find a GPS antenna for the Logic PD board I have,
but it occurred to me that the kit that I have used may require
special GPS firmware.  Are you using any special firmware in the radio
in conjunction with these driver patches or are you using the standard
bts files?

adam
> ---
>  drivers/gnss/Kconfig  |  13 ++
>  drivers/gnss/Makefile |   3 +
>  drivers/gnss/ai2.c    | 523 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 539 insertions(+)
>  create mode 100644 drivers/gnss/ai2.c
>
> diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
> index d7fe265c28696..3a20212dacc9e 100644
> --- a/drivers/gnss/Kconfig
> +++ b/drivers/gnss/Kconfig
> @@ -65,4 +65,17 @@ config GNSS_USB
>
>           If unsure, say N.
>
> +config GNSS_AI2
> +       tristate "TI AI2 procotol support"
> +       depends on BT_HCIUART_LL
> +       help
> +         Say Y here if you have a Texas Instruments Wilink combo chip
> +         contaning among other things a GNSS receiver speaking the

s/contaning /containing

> +         Air Independent Interface (AI2) protocol.
> +
> +         To compile this driver as a module, choose M here: the module will
> +         be called gnss-ai2.
> +
> +         If unsure, say N.
> +
>  endif # GNSS
> diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
> index bb2cbada34359..bf6fefcb2e823 100644
> --- a/drivers/gnss/Makefile
> +++ b/drivers/gnss/Makefile
> @@ -20,3 +20,6 @@ gnss-ubx-y := ubx.o
>
>  obj-$(CONFIG_GNSS_USB)                 += gnss-usb.o
>  gnss-usb-y := usb.o
> +
> +obj-$(CONFIG_GNSS_AI2)                 += gnss-ai2.o
> +gnss-ai2-y := ai2.o
> diff --git a/drivers/gnss/ai2.c b/drivers/gnss/ai2.c
> new file mode 100644
> index 0000000000000..673fbe8de7ef2
> --- /dev/null
> +++ b/drivers/gnss/ai2.c
> @@ -0,0 +1,523 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Texas Instruments AI2 (Air independent interface) protocol device driver
> + * Used for some TI WLAN/Bluetooth/GNSS combo chips.
> + *
> + * Copyright (C) 2024 Andreas Kemnade <andreas@kemnade.info>
> + */
> +#include <linux/errno.h>
> +#include <linux/gnss.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/ti_wilink_st.h>
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +
> +/* Channel-9 details for GPS */
> +#define GPS_CH9_PKT_NUMBER             0x9
> +#define GPS_CH9_OP_WRITE               0x1
> +#define GPS_CH9_OP_READ                        0x2
> +#define GPS_CH9_OP_COMPLETED_EVT       0x3
> +
> +/* arbitarily chosen, should fit everything seen in the past */
> +#define MAX_AI2_FRAME_SIZE 2048
> +
> +#define AI2_ESCAPE 0x10 /* if sent as data, it is doubled */
> +#define AI2_END_MARKER 0x3
> +#define AI2_ACK 0x2
> +
> +/* reports */
> +#define AI2_REPORT_NMEA 0xd3
> +
> +#define NMEA_HEADER_LEN 4
> +
> +/* commands */
> +#define AI2_CMD_RECEIVER_STATE 2
> +
> +#define RECEIVER_STATE_OFF 1
> +#define RECEIVER_STATE_IDLE 2
> +#define RECEIVER_STATE_ON 3
> +
> +#define AI2_CMD_CONFIG_NMEA 0xe5
> +#define NMEA_MASK_GGA (1 << 0)
> +#define NMEA_MASK_GLL (1 << 1)
> +#define NMEA_MASK_GSA (1 << 2)
> +#define NMEA_MASK_GSV (1 << 3)
> +#define NMEA_MASK_RMC (1 << 4)
> +#define NMEA_MASK_VTG (1 << 5)
> +
> +#define NMEA_MASK_ALL (NMEA_MASK_GGA | \
> +               NMEA_MASK_GLL | \
> +               NMEA_MASK_GSA | \
> +               NMEA_MASK_GSV | \
> +               NMEA_MASK_RMC | \
> +               NMEA_MASK_VTG)
> +
> +
> +static bool ai2raw;
> +
> +struct ai2_device {
> +       struct mutex gdev_mutex;
> +       bool gdev_open;
> +       struct gnss_device *gdev;
> +       struct device *dev;
> +       struct sk_buff *recv_skb;
> +       bool recv_esc;
> +};
> +
> +static struct sk_buff *ai2_skb_alloc(unsigned int len, gfp_t how)
> +{
> +       struct sk_buff *skb;
> +
> +       skb = bt_skb_alloc(len + sizeof(struct gps_event_hdr), how);
> +       if (skb)
> +               skb_reserve(skb, sizeof(struct gps_event_hdr));
> +
> +       return skb;
> +}
> +
> +static int ai2_send_frame(struct ai2_device *ai2dev,
> +                         struct sk_buff *skb)
> +{
> +       int len;
> +       struct gps_event_hdr *gnssdrv_hdr;
> +       struct hci_dev *hdev;
> +
> +       if (skb->len >= U16_MAX)
> +               return -EINVAL;
> +
> +       /*
> +        * note: fragmentation at this point not handled yet
> +        * not needed for simple config commands
> +        */
> +       len = skb->len;
> +       gnssdrv_hdr = skb_push(skb, sizeof(struct gps_event_hdr));
> +       gnssdrv_hdr->opcode = GPS_CH9_OP_WRITE;
> +       gnssdrv_hdr->plen = __cpu_to_le16(len);
> +
> +       hci_skb_pkt_type(skb) = GPS_CH9_PKT_NUMBER;
> +       hdev = st_get_hci(ai2dev->dev->parent);
> +       return hdev->send(hdev, skb);
> +}
> +
> +static void ai2_put_escaped(struct sk_buff *skb, u8 d)
> +{
> +       skb_put_u8(skb, d);
> +       if (d == 0x10)
> +               skb_put_u8(skb, d);
> +}
> +
> +static struct sk_buff *ai2_compose_frame(bool request_ack,
> +                                       u8 cmd,
> +                                       const u8 *data,
> +                                       int len)
> +{
> +       u16 sum;
> +       int i;
> +       /* duplicate the length to have space for worst case escaping */
> +       struct sk_buff *skb = ai2_skb_alloc(2 + len * 2 + 2 + 2, GFP_KERNEL);
> +
> +       skb_put_u8(skb, AI2_ESCAPE);
> +       skb_put_u8(skb, request_ack ? 1 : 0);
> +
> +       sum = AI2_ESCAPE;
> +       if (request_ack)
> +               sum++;
> +
> +       ai2_put_escaped(skb, cmd);
> +       sum += cmd;
> +
> +       ai2_put_escaped(skb, len & 0xff);
> +       sum += len & 0xff;
> +
> +       ai2_put_escaped(skb, len >> 8);
> +       sum += len >> 8;
> +
> +       for (i = 0; i < len; i++) {
> +               sum += data[i];
> +               ai2_put_escaped(skb, data[i]);
> +       }
> +
> +       ai2_put_escaped(skb, sum & 0xFF);
> +       ai2_put_escaped(skb, sum >> 8);
> +       skb_put_u8(skb, AI2_ESCAPE);
> +       skb_put_u8(skb, AI2_END_MARKER);
> +
> +       return skb;
> +}
> +
> +static int ai2_set_receiver_state(struct ai2_device *ai2dev,
> +                                             uint8_t state)
> +{
> +       struct sk_buff *skb = ai2_compose_frame(true, AI2_CMD_RECEIVER_STATE,
> +                                               &state, 1);
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       return ai2_send_frame(ai2dev, skb);
> +}
> +
> +static int ai2_config_nmea_reports(struct ai2_device *ai2dev,
> +                                  uint8_t mask)
> +{
> +       u8 buf[4] = {0};
> +       struct sk_buff *skb;
> +
> +       buf[0] = mask;
> +       skb = ai2_compose_frame(true, AI2_CMD_CONFIG_NMEA,
> +                               buf, sizeof(buf));
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       return ai2_send_frame(ai2dev, skb);
> +}
> +
> +/*
> + * Unknown commands, give some version information, must be sent
> + * once, not sure what undoes them besides resetting the whole
> + * bt part, but no sings of significant things being still

s/sings/signs

> + * turned on without undoing this.
> + */
> +static int gnss_ai2_init(struct ai2_device *ai2dev)
> +{
> +       int ret;
> +       u8 d = 0x01;
> +       struct sk_buff *skb = ai2_compose_frame(true, 0xf5, &d, 1);
> +
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       ret = ai2_send_frame(ai2dev, skb);
> +       if (ret)
> +               return ret;
> +
> +       msleep(200);
> +       d = 5;
> +       skb = ai2_compose_frame(true, 0xf1, &d, 1);
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       return ai2_send_frame(ai2dev, skb);
> +}
> +
> +static int gnss_ai2_open(struct gnss_device *gdev)
> +{
> +       struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +       int ret;
> +
> +       mutex_lock(&ai2dev->gdev_mutex);
> +       ai2dev->gdev_open = true;
> +       mutex_unlock(&ai2dev->gdev_mutex);
> +       if (ai2raw)
> +               return 0;
> +
> +       ret = gnss_ai2_init(ai2dev);
> +       if (ret)
> +               goto err;
> +
> +       /* TODO: find out on what kind of ack we should wait */
> +       msleep(200);
> +       ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
> +       if (ret)
> +               goto err;
> +
> +       msleep(200);
> +       ret = ai2_config_nmea_reports(ai2dev, NMEA_MASK_ALL);
> +       if (ret)
> +               goto err;
> +
> +       msleep(200);
> +       ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_ON);
> +       if (ret)
> +               goto err;
> +
> +       return 0;
> +err:
> +       mutex_lock(&ai2dev->gdev_mutex);
> +       ai2dev->gdev_open = false;
> +       if (ai2dev->recv_skb)
> +               kfree_skb(ai2dev->recv_skb);
> +
> +       ai2dev->recv_skb = NULL;
> +       mutex_unlock(&ai2dev->gdev_mutex);
> +       return ret;
> +}
> +
> +static void gnss_ai2_close(struct gnss_device *gdev)
> +{
> +       struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +
> +       /* TODO: find out on what kind of ack we should wait */
> +       if (!ai2raw) {
> +               msleep(200);
> +               ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE);
> +               msleep(200);
> +               ai2_set_receiver_state(ai2dev, RECEIVER_STATE_OFF);
> +               msleep(200);
> +       }
> +
> +       mutex_lock(&ai2dev->gdev_mutex);
> +       ai2dev->gdev_open = false;
> +       if (ai2dev->recv_skb)
> +               kfree_skb(ai2dev->recv_skb);
> +
> +       ai2dev->recv_skb = NULL;
> +       mutex_unlock(&ai2dev->gdev_mutex);
> +}
> +
> +
> +static int gnss_ai2_write_raw(struct gnss_device *gdev,
> +               const unsigned char *buf, size_t count)
> +{
> +       struct ai2_device *ai2dev = gnss_get_drvdata(gdev);
> +       int err = 0;
> +       struct sk_buff *skb = NULL;
> +
> +       if (!ai2raw)
> +               return -EPERM;
> +
> +       /* allocate packet */
> +       skb = ai2_skb_alloc(count, GFP_KERNEL);
> +       if (!skb) {
> +               BT_ERR("cannot allocate memory for HCILL packet");
> +               err = -ENOMEM;
> +               goto out;
> +       }
> +
> +       skb_put_data(skb, buf, count);
> +
> +       err = ai2_send_frame(ai2dev, skb);
> +       if (err)
> +               goto out;
> +
> +       return count;
> +out:
> +       return err;
> +}
> +
> +static const struct gnss_operations gnss_ai2_ops = {
> +       .open           = gnss_ai2_open,
> +       .close          = gnss_ai2_close,
> +       .write_raw      = gnss_ai2_write_raw,
> +};
> +
> +static void process_ai2_packet(struct ai2_device *ai2dev,
> +                              u8 cmd, u8 *data, u16 len)
> +{
> +       if (cmd != AI2_REPORT_NMEA)
> +               return;
> +
> +       if (len <= NMEA_HEADER_LEN)
> +               return;
> +
> +       len -= NMEA_HEADER_LEN;
> +       data += NMEA_HEADER_LEN;
> +
> +       gnss_insert_raw(ai2dev->gdev, data, len);
> +}
> +
> +/* do some sanity checks and split frame into packets */
> +static void process_ai2_frame(struct ai2_device *ai2dev)
> +{
> +       u16 sum;
> +       int i;
> +       u8 *head;
> +       u8 *data;
> +
> +       sum = 0;
> +       data = ai2dev->recv_skb->data;
> +       for (i = 0; i < ai2dev->recv_skb->len - 2; i++)
> +               sum += data[i];
> +
> +       print_hex_dump_bytes("ai2 frame: ", DUMP_PREFIX_OFFSET, data, ai2dev->recv_skb->len);
> +
> +       if (get_unaligned_le16(data + i) != sum) {
> +               dev_dbg(ai2dev->dev,
> +                       "checksum error in reception, dropping frame\n");
> +               return;
> +       }
> +
> +       /* reached if byte 1 in the command packet is set to 1 */
> +       if (data[1] == AI2_ACK)
> +               return;
> +
> +       head = skb_pull(ai2dev->recv_skb, 2); /* drop frame start marker */
> +       while (head && (ai2dev->recv_skb->len >= 3)) {
> +               u8 cmd;
> +               u16 pktlen;
> +
> +               cmd = head[0];
> +               pktlen = get_unaligned_le16(head + 1);
> +               data = skb_pull(ai2dev->recv_skb, 3);
> +               if (!data)
> +                       break;
> +
> +               if (pktlen > ai2dev->recv_skb->len)
> +                       break;
> +
> +               head = skb_pull(ai2dev->recv_skb, pktlen);
> +
> +               process_ai2_packet(ai2dev, cmd, data, pktlen);
> +       }
> +}
> +
> +static void process_ai2_data(struct ai2_device *ai2dev,
> +                            u8 *data, int len)
> +{
> +       int i;
> +
> +       for (i = 0; i < len; i++) {
> +               if (!ai2dev->recv_skb) {
> +                       ai2dev->recv_esc = false;
> +                       if (data[i] != AI2_ESCAPE) {
> +                               dev_dbg(ai2dev->dev, "dropping data, trying to resync\n");
> +                               continue;
> +                       }
> +                       ai2dev->recv_skb = alloc_skb(MAX_AI2_FRAME_SIZE, GFP_KERNEL);
> +                       if (!ai2dev->recv_skb)
> +                               return;
> +
> +                       dev_dbg(ai2dev->dev, "starting packet\n");
> +
> +                       /* this initial AI2_ESCAPE is part of checksum computation */
> +                       skb_put_u8(ai2dev->recv_skb, data[i]);
> +                       continue;
> +               }
> +               if (ai2dev->recv_skb->len == 1) {
> +                       if (data[i] == AI2_END_MARKER) {
> +                               dev_dbg(ai2dev->dev, "unexpected end of frame received\n");
> +                               kfree_skb(ai2dev->recv_skb);
> +                               ai2dev->recv_skb = NULL;
> +                               continue;
> +                       }
> +                       skb_put_u8(ai2dev->recv_skb, data[i]);
> +               } else {
> +                       /* drop one of two AI2_ESCAPE */
> +                       if ((!ai2dev->recv_esc) &&
> +                          (data[i] == AI2_ESCAPE)) {
> +                               ai2dev->recv_esc = true;
> +                               continue;
> +                       }
> +
> +                       if (ai2dev->recv_esc &&
> +                           (data[i] == AI2_END_MARKER)) {
> +                               process_ai2_frame(ai2dev);
> +                               kfree_skb(ai2dev->recv_skb);
> +                               ai2dev->recv_skb = NULL;
> +                               continue;
> +                       }
> +                       skb_put_u8(ai2dev->recv_skb, data[i]);
> +               }
> +       }
> +}
> +
> +static void gnss_recv_frame(struct device *dev, struct sk_buff *skb)
> +{
> +       struct ai2_device *ai2dev = dev_get_drvdata(dev);
> +       struct gps_event_hdr *gnss_hdr;
> +       u8 *data;
> +
> +       if (!ai2dev->gdev) {
> +               kfree_skb(skb);
> +               return;
> +       }
> +
> +       gnss_hdr = (struct gps_event_hdr *)skb->data;
> +
> +       data = skb_pull(skb, sizeof(*gnss_hdr));
> +       /*
> +        * REVISIT: maybe do something with the completed
> +        * event
> +        */
> +       if (gnss_hdr->opcode == GPS_CH9_OP_READ) {
> +               mutex_lock(&ai2dev->gdev_mutex);
> +               if (ai2dev->gdev_open) {
> +                       if (ai2raw)
> +                               gnss_insert_raw(ai2dev->gdev, data, skb->len);
> +                       else
> +                               process_ai2_data(ai2dev, data, skb->len);
> +               } else {
> +                       dev_dbg(ai2dev->dev,
> +                               "receiving data while chip should be off\n");
> +               }
> +               mutex_unlock(&ai2dev->gdev_mutex);
> +       }
> +       kfree_skb(skb);
> +}
> +
> +static int gnss_ai2_probe(struct platform_device *pdev)
> +{
> +       struct gnss_device *gdev;
> +       struct ai2_device *ai2dev;
> +       int ret;
> +
> +       ai2dev = devm_kzalloc(&pdev->dev, sizeof(*ai2dev), GFP_KERNEL);
> +       if (!ai2dev)
> +               return -ENOMEM;
> +
> +       ai2dev->dev = &pdev->dev;
> +       gdev = gnss_allocate_device(&pdev->dev);
> +       if (!gdev)
> +               return -ENOMEM;
> +
> +       gdev->ops = &gnss_ai2_ops;
> +       gdev->type = ai2raw ? GNSS_TYPE_AI2 : GNSS_TYPE_NMEA;
> +       gnss_set_drvdata(gdev, ai2dev);
> +       platform_set_drvdata(pdev, ai2dev);
> +       st_set_gnss_recv_func(pdev->dev.parent, gnss_recv_frame);
> +       mutex_init(&ai2dev->gdev_mutex);
> +
> +       ret = gnss_register_device(gdev);
> +       if (ret)
> +               goto err;
> +
> +       ai2dev->gdev = gdev;
> +       return 0;
> +
> +err:
> +       st_set_gnss_recv_func(pdev->dev.parent, NULL);
> +
> +       if (ai2dev->recv_skb)
> +               kfree_skb(ai2dev->recv_skb);
> +
> +       gnss_put_device(gdev);
> +       return ret;
> +}
> +
> +static void gnss_ai2_remove(struct platform_device *pdev)
> +{
> +       struct ai2_device *ai2dev =  platform_get_drvdata(pdev);
> +
> +       st_set_gnss_recv_func(pdev->dev.parent, NULL);
> +       gnss_deregister_device(ai2dev->gdev);
> +       gnss_put_device(ai2dev->gdev);
> +       if (ai2dev->recv_skb)
> +               kfree_skb(ai2dev->recv_skb);
> +}
> +
> +static const struct platform_device_id gnss_ai2_id[] = {
> +       {
> +               .name = "ti-ai2-gnss"
> +       }, {
> +               /* sentinel */
> +       }
> +};
> +MODULE_DEVICE_TABLE(platform, gnss_ai2_id);
> +
> +static struct platform_driver gnss_ai2_driver = {
> +       .driver = {
> +               .name = "gnss-ai2",
> +       },
> +       .probe          = gnss_ai2_probe,
> +       .remove_new     = gnss_ai2_remove,
> +       .id_table       = gnss_ai2_id,
> +};
> +module_platform_driver(gnss_ai2_driver);
> +
> +module_param(ai2raw, bool, 0600);
> +MODULE_DESCRIPTION("AI2 GNSS driver");
> +MODULE_LICENSE("GPL");
> --
> 2.39.2
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-02-13 13:57   ` Adam Ford
@ 2024-02-13 19:20     ` Andreas Kemnade
  0 siblings, 0 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-02-13 19:20 UTC (permalink / raw)
  To: Adam Ford
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Tony Lindgren, tomi.valkeinen,
	Péter Ujfalusi, robh, hns

On Tue, 13 Feb 2024 07:57:10 -0600
Adam Ford <aford173@gmail.com> wrote:

> On Sun, Jan 28, 2024 at 11:34 AM Andreas Kemnade <andreas@kemnade.info> wrote:
> >
> > Add a driver for the Air Independent Interface protocol used by some TI
> > Wilink combo chips. Per default, send out just NMEA to userspace and turn
> > on/off things at open()/close() but keep the door open for any
> > sophisticated development regarding the AI2 protocol by having a kernel
> > parameter to turn it into raw mode resembling /dev/tigps provided by some
> > TI vendor kernels.
> >
> > Signed-off-by: Andreas Kemnade <andreas@kemnade.info>  
> 
> I am still trying to find a GPS antenna for the Logic PD board I have,
> but it occurred to me that the kit that I have used may require
> special GPS firmware.  Are you using any special firmware in the radio
> in conjunction with these driver patches or are you using the standard
> bts files?
> 
Just
https://github.com/TI-ECS/bt-firmware/raw/master/TIInit_10.6.15.bts

I removed one command (by setting type to commment) regarding some clock
configuration.

Well, maybe you see NMEA without antenna, maybe you get an error message
because the module "feels" the absence of an antenna...

Regards,
Andreas

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol
  2024-01-29  7:53   ` Paul Menzel
  2024-01-29 20:53     ` Andreas Kemnade
@ 2024-02-25 22:16     ` Andreas Kemnade
  1 sibling, 0 replies; 12+ messages in thread
From: Andreas Kemnade @ 2024-02-25 22:16 UTC (permalink / raw)
  To: Paul Menzel
  Cc: marcel, johan.hedberg, luiz.dentz, johan, jirislaby, gregkh,
	linux-kernel, linux-bluetooth, Adam Ford, Tony Lindgren,
	tomi.valkeinen, Péter Ujfalusi, robh, hns

Hi Paul,

I have left out one thing to answer.

On Mon, 29 Jan 2024 08:53:27 +0100
Paul Menzel <pmenzel@molgen.mpg.de> wrote:

> > +	ret = ai2_send_frame(ai2dev, skb);
> > +	if (ret)
> > +		return ret;
> > +
> > +	msleep(200);  
> 
> I’d be great if you added a comment, why this long delay is needed.

Well, I am just a bit paranoid, s/msleep/wait_for_completion_timeout/ and
complete() on some ack would probably improve this. I just hesitated to add
something like this until I have the feeling that I do not need to turn the
driver upside-down.

Regards,
Andreas

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2024-02-25 22:17 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-28 17:33 [RFC PATCH v2 0/3] bluetooth/gnss: GNSS support for TiWi chips Andreas Kemnade
2024-01-28 17:33 ` [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips Andreas Kemnade
2024-01-29  7:40   ` Paul Menzel
2024-01-29 20:34     ` Andreas Kemnade
2024-01-28 17:33 ` [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips Andreas Kemnade
2024-01-29  7:40   ` Paul Menzel
2024-01-28 17:33 ` [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Andreas Kemnade
2024-01-29  7:53   ` Paul Menzel
2024-01-29 20:53     ` Andreas Kemnade
2024-02-25 22:16     ` Andreas Kemnade
2024-02-13 13:57   ` Adam Ford
2024-02-13 19:20     ` Andreas Kemnade

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).