From patchwork Sun Jan 28 17:33:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Kemnade X-Patchwork-Id: 768601 Received: from mail.andi.de1.cc (mail.andi.de1.cc [178.238.236.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7086833CF1; Sun, 28 Jan 2024 18:06:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.238.236.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465188; cv=none; b=EfvRqi1Hu7pu075WPjafocbkE5Rs2Uz7mOcRYKwWHJbviRZDul5+9C5UTsAxTh7XDGllegECgJ+x0XYpRR5ZdbkK1WvvUUE3b5nQyI6B1BUkHQCkjondQGGEdyzBYf0n3HypjLqHrIH1vWUaKhJQxqKwar8r3of/b5IwK4QkPSg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465188; c=relaxed/simple; bh=xeJpHy5p3TX6HUTpy0hNf1X9TiuUySjV1G3KdJuEz+Y=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=mvyMgxCqy8P9QQRtFYR06IBR0bynJSZBU22UwugTa66zw1S762n3bEiS9V3CQzT5D2Io7viClC8LbvrPkhYRURRc5oQq3dgTMEspDBccFBYNrD8CDkaSpJzKNrDLcnqwA5jalEPy9HrfVcxDdEM45+KPSSkDKvj7zk2XLLjt1qk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info; spf=pass smtp.mailfrom=kemnade.info; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b=HjfMgtol; arc=none smtp.client-ip=178.238.236.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=kemnade.info Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b="HjfMgtol" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=kemnade.info; s=20220719; h=Content-Transfer-Encoding:MIME-Version: References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc: Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=uy766eRmu4f/14ST1SkrTm7XvdpFq6LpMkMB0sq2fWw=; b=HjfMgtolvJlys2ez29qkSflDmd ccHmjqp2JJgP++WDwMdNUxiEh74O3ZxEdBXCSB2lK9XqVOHfJZypmVaeJbzvy3vHgGa5d6glhUIcq +rSPqwoTKiWZblDBd5b/GIn89zSGl0RSWYxJe3S7eR4RmSErUio3sPYUOTx5mkfyrdBxix5OucT4I AL54Qd3E3G0az2w09lbmLvNaspm55hltm5h76WHQSw05fhLPD5cFpNp67uh704JG4z8em3du9ByXM 95jFh4QNEuGvO9ABfeb2sXZ6WVLso9M4vayXL3BW8/S3eYTqc4taROSrUjQxFvHxMcjS1B5FdrVhC 1NXTPAnA==; Received: from p200300c2070939001a3da2fffebfd33a.dip0.t-ipconnect.de ([2003:c2:709:3900:1a3d:a2ff:febf:d33a] helo=aktux) by mail.andi.de1.cc with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rU92b-008qDa-Us; Sun, 28 Jan 2024 18:33:57 +0100 Received: from andi by aktux with local (Exim 4.96) (envelope-from ) id 1rU92b-00BO9h-0t; Sun, 28 Jan 2024 18:33:57 +0100 From: Andreas Kemnade To: marcel@holtmann.org, johan.hedberg@gmail.com, luiz.dentz@gmail.com, johan@kernel.org, jirislaby@kernel.org, andreas@kemnade.info, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org, Adam Ford , Tony Lindgren , tomi.valkeinen@ideasonboard.com, =?utf-8?q?P=C3=A9ter_Ujfalusi?= , robh@kernel.org, hns@goldelico.com Subject: [RFC PATCH v2 1/3] gnss: Add AI2 protocol used by some TI combo chips. Date: Sun, 28 Jan 2024 18:33:50 +0100 Message-Id: <20240128173352.2714442-2-andreas@kemnade.info> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240128173352.2714442-1-andreas@kemnade.info> References: <20240128173352.2714442-1-andreas@kemnade.info> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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 }; From patchwork Sun Jan 28 17:33:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Kemnade X-Patchwork-Id: 767257 Received: from mail.andi.de1.cc (mail.andi.de1.cc [178.238.236.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 69A722E62A; Sun, 28 Jan 2024 18:06:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.238.236.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465178; cv=none; b=r/YyvXC37SGGoenAN+U5cHPmLn+xTcw5mxI52WajVx/m2luwGmxcIbhqs/td1OxAHxBUp3xvr02X6z8lKwvPfduHsbtTc6ey78El1k4DwHWeEgAZwD3gxjBhXe1ZSxcXJf0tIBAmyiNZOU/Xtzl2ExDrQti82pfvnvklmAtgS0g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465178; c=relaxed/simple; bh=sS1ZWEmQKtZR7kwdEZ+vuqXJLlJ/VTALKyjf/CpHz2U=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=QFPFBzm6b9jC9bScAjKUKF4Q76vCij9cJvgeI19j8sIGMDzrVANt2+QQAxk83Uz5+42+WF6fv6Ag1S7ZLQroFpVbCSKzAqJiWwyf4sKt8Ey+h80NTSyEoHPsJZvqX3UBLg8ze2aCGchTfyDm+HvDL7EltgPx/l75t6D4vxKpqsc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info; spf=pass smtp.mailfrom=kemnade.info; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b=l5YE3eTz; arc=none smtp.client-ip=178.238.236.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=kemnade.info Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b="l5YE3eTz" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=kemnade.info; s=20220719; h=Content-Transfer-Encoding:MIME-Version: References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc: Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=Wqej4+vmTV/rU8AtrwDFmSZQizv8btuEV2EtfTfvd/8=; b=l5YE3eTzYsQYcO7x3N661sRzKX +7qFZ8/zRZBqVG4a41b+F0JEGCvLlrboPFIxoolUf5AAkY4UymjGAJyxygTJxBrT74t3L27hUr1TN k4LflZytG+bZQ9bh+KkaIKPM97N61dzLkkxPiy3XOO5TVrna70FgzB4+rFvkFWc6BmjhxHUdqOITo kzIha6oJuWiT6OD/qy49BqNW1lu/zIADbS1qwAiXmxEe22mcUwY1FYuEmubYic0WCSSl60dUQR2IY h3kQmewRVuflREZuokofcnhidqsW5JxIxl+hz2n7DBohwsSrI0nC0nrNd2OWb4VAsaG0Whrr4m3R6 Yzmq/Frw==; Received: from p200300c2070939001a3da2fffebfd33a.dip0.t-ipconnect.de ([2003:c2:709:3900:1a3d:a2ff:febf:d33a] helo=aktux) by mail.andi.de1.cc with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rU92c-008qDd-CQ; Sun, 28 Jan 2024 18:33:58 +0100 Received: from andi by aktux with local (Exim 4.96) (envelope-from ) id 1rU92b-00BO9l-2W; Sun, 28 Jan 2024 18:33:57 +0100 From: Andreas Kemnade To: marcel@holtmann.org, johan.hedberg@gmail.com, luiz.dentz@gmail.com, johan@kernel.org, jirislaby@kernel.org, andreas@kemnade.info, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org, Adam Ford , Tony Lindgren , tomi.valkeinen@ideasonboard.com, =?utf-8?q?P=C3=A9ter_Ujfalusi?= , robh@kernel.org, hns@goldelico.com Subject: [RFC PATCH v2 2/3] bluetooth: ti-st: Add GNSS subdevice for TI Wilink chips Date: Sun, 28 Jan 2024 18:33:51 +0100 Message-Id: <20240128173352.2714442-3-andreas@kemnade.info> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240128173352.2714442-1-andreas@kemnade.info> References: <20240128173352.2714442-1-andreas@kemnade.info> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 Reviewed-by: Paul Menzel --- 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 #include #include +#include #include #include #include @@ -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 From patchwork Sun Jan 28 17:33:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andreas Kemnade X-Patchwork-Id: 767256 Received: from mail.andi.de1.cc (mail.andi.de1.cc [178.238.236.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 670E031A93; Sun, 28 Jan 2024 18:06:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.238.236.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465185; cv=none; b=IRVVtVQYne0mErcATF5ihqPpyzOQmpdaqaU7Pzkw2MZVh+GwAXQPvTxI+ymxqZm0ZvUB1DgdzwIQXwrNa+jVKgoJk+2NPAGC0d+yqEjeRtkefp9pVvX7U4scHwg4G02nwhyPRFPRWrDDZ4cv0bjbl/rezk5cBzEXQ5Y3cidT7tw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1706465185; c=relaxed/simple; bh=YP4DCjBYpH7ccix1hAudgY/BbFg5AWuJy6O0mPtkRXc=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ak3qj+Ur+ouKua/Hn1nDS+bWgXNiZmSm8S2IgLE8zDUKwil8Ut6Reot3uPDxRXBCOZHnNdpH0fzg3AoeOIFf4nZHjknj/FsgP9UmG05t76I1twIPfXp4Mc34fB6CFLndLkF2yNfiYag2JLSVAhkwXLVF5P6usnL4vuhD2pxYLkY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info; spf=pass smtp.mailfrom=kemnade.info; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b=TgdyQ70h; arc=none smtp.client-ip=178.238.236.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=kemnade.info Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=kemnade.info Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b="TgdyQ70h" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=kemnade.info; s=20220719; h=Content-Transfer-Encoding:MIME-Version: References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc: Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help: List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=0DHfkBV7zUs0A+EDA7fgMc09/iDpP0RgPkI5c0elcYo=; b=TgdyQ70hRdYODYYy/OAwjqe9un AUbkWMmniebAkN0xh911ZhiiEjuWUcb+zLBl64PLc9sP8+4hZnig8zhja4rO96hd81F7Ie1sOs4qS XsZ6fBnJj7IjuKO2jA5n7xEEwvKbhWoHDJt4DMl3QTQVNzBrccEHQuZGRFdC9Y3q+ZMzElhMKVt3+ t2NcCcn4dE3SUFyiCW51nAYYuAsLZj7tGvcguD+F6jMBdXZK1lFoSApkSwOLPcPlEWqoWnQC3bsPq bpy8ybYdfn/gqRs8qkeN7gKSDsEjVlF3LkQt4s3AVlFwi3/G2MEhDfELHJRCou5UWfbqBt2Htmzgb dguu3eeA==; Received: from p200300c2070939001a3da2fffebfd33a.dip0.t-ipconnect.de ([2003:c2:709:3900:1a3d:a2ff:febf:d33a] helo=aktux) by mail.andi.de1.cc with esmtpsa (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rU92d-008qDf-3U; Sun, 28 Jan 2024 18:33:59 +0100 Received: from andi by aktux with local (Exim 4.96) (envelope-from ) id 1rU92c-00BO9r-1H; Sun, 28 Jan 2024 18:33:58 +0100 From: Andreas Kemnade To: marcel@holtmann.org, johan.hedberg@gmail.com, luiz.dentz@gmail.com, johan@kernel.org, jirislaby@kernel.org, andreas@kemnade.info, gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org, Adam Ford , Tony Lindgren , tomi.valkeinen@ideasonboard.com, =?utf-8?q?P=C3=A9ter_Ujfalusi?= , robh@kernel.org, hns@goldelico.com Subject: [RFC PATCH v2 3/3] gnss: Add driver for AI2 protocol Date: Sun, 28 Jan 2024 18:33:52 +0100 Message-Id: <20240128173352.2714442-4-andreas@kemnade.info> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240128173352.2714442-1-andreas@kemnade.info> References: <20240128173352.2714442-1-andreas@kemnade.info> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 Acked-by: Paul Menzel --- 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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");