Received: by 2002:ab2:7903:0:b0:1fb:b500:807b with SMTP id a3csp1011998lqj; Mon, 3 Jun 2024 07:45:36 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCXZ1dD36r76YQEkzOfTAWmoIOp7h8/pCsXyPB9vqv2Uev+BdQ+MlMU0fbkg91EBzVhHOBwP5GQ+x9sGHEtNFmZ4sh1nZtncCLh5/LMqGg== X-Google-Smtp-Source: AGHT+IFwKmJ3Fw+RHh90O2ewm/VhjVx1rig3qalK528yHfMNGPegyeHLfeq6OmkJOmDweul+b+Gi X-Received: by 2002:a17:906:1791:b0:a68:44a0:a808 with SMTP id a640c23a62f3a-a6844a0a977mr546832466b.29.1717425935910; Mon, 03 Jun 2024 07:45:35 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1717425935; cv=pass; d=google.com; s=arc-20160816; b=iP/HBUnr1d4SJnO0lNQk0KI1QDytf6qB0PNsWsHVjDn4JOe7pUfDaCitBrBA5RIsB+ 02oja3MGbAPzAO30Y3iUUi0fgz3X0VQlqMbf+KfAuB+mb8ZEAM3rEgk2xFtdFzDGRsXT qsZXYMdbo9Qp9ODIx6e6bIcvZ25xsZCaPQR027+DWOyHuaMnECTLrDN2/67miRK4QAiR 8J/U9nckhHfMh+uHgBORmKDLpoNC1c6Yyu11tdTaGwn+0EtZJO8Vx05fca5c1TZ063N7 h+8PzxNrB0qUPzoLeXjdejNtDHPyqdMscMw/ISUato1ZGWTyROePjVII1bj5zABh01ti Cw0w== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:list-unsubscribe :list-subscribe:list-id:precedence:references:in-reply-to:message-id :date:subject:to:from:dkim-signature; bh=drKHUgwKkW2PbyIoNGASeFX7RLuVgwpvtctgYrIf9qo=; fh=XiJfjSFyRm5tGTtOT2sw9gKCQj2gMqw2SDumfkMcVDA=; b=VCrBU4wWSM2myVADX5QUnOMjRqZJw8w7gdp+aUyp18XYkU2rkwf2oRg5MBS/kbDj5J EgNUJq/T7WCxm7jIcn/nrTac6eQamQ6PPZqlCLLEthuAx6suMXerWVEOMfZjCB7HWLPj p1Vi/KnymV2oZ/DgltPLIJ6SeFFAUerzwONIrPhaK4skosll2vrlxWB4Xla9sOfZK0Px rkuAvH3eRiWMlyMYCTVHMw6dpr7yuc4Esai0QEHRR/WNbQP4b3z6j5wK0Qh1YUDPZFi+ t6vnO90uZ1hsC4xiEohRo7bwg73Ud01lLjolfBw4CvCNlutAcJjkEstLoRc6S5Pd6h9A y5Ng==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=fail header.i=@kemnade.info header.s=20220719 header.b=MS5EINeh; arc=pass (i=1 spf=pass spfdomain=kemnade.info dkim=pass dkdomain=kemnade.info); spf=pass (google.com: domain of linux-bluetooth+bounces-5083-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-bluetooth+bounces-5083-linux.lists.archive=gmail.com@vger.kernel.org" Return-Path: Received: from am.mirrors.kernel.org (am.mirrors.kernel.org. [2604:1380:4601:e00::3]) by mx.google.com with ESMTPS id a640c23a62f3a-a6906852577si121131466b.951.2024.06.03.07.45.35 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Jun 2024 07:45:35 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-bluetooth+bounces-5083-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) client-ip=2604:1380:4601:e00::3; Authentication-Results: mx.google.com; dkim=fail header.i=@kemnade.info header.s=20220719 header.b=MS5EINeh; arc=pass (i=1 spf=pass spfdomain=kemnade.info dkim=pass dkdomain=kemnade.info); spf=pass (google.com: domain of linux-bluetooth+bounces-5083-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-bluetooth+bounces-5083-linux.lists.archive=gmail.com@vger.kernel.org" Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id 4BF0D1F22D6B for ; Mon, 3 Jun 2024 14:45:35 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 531B1132804; Mon, 3 Jun 2024 14:44:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=kemnade.info header.i=@kemnade.info header.b="MS5EINeh" X-Original-To: linux-bluetooth@vger.kernel.org Received: from mail2.andi.de1.cc (vmd64148.contaboserver.net [161.97.139.27]) (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 B51DC1311A8; Mon, 3 Jun 2024 14:44:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=161.97.139.27 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1717425875; cv=none; b=u08irMra2KH5cDTKwzEoaOk5rh4lut2qnb9kEGlPzPUu75kATwXvOqJGS6vOIiI9vdO2F9ypDb5AJCzT4uQhgaWEl780+JXtdvdxbyjkIumxNM8vUpdc5ucyeCXSvHtcxBQ8KgTnlHglk3tBlZsNDqf767mwxqCUuhK59zdZ3M0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1717425875; c=relaxed/simple; bh=JqkpiY/+hZD2WCNVH3McGoAN9RlplThIwX7uduQlb3k=; h=From:To:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=a2TkOPv++La18QTZQWPdlI1xMPT6mjT0fJeQJBls6AcDT+sKia/v5oynxVhHVDoLkJxEB1s7DZs/pN1tLsOIoMXOjgpJl8a+CwV/20ydLho2kNsZvz7YZ3fYG3NEzgZUTxpzFe1fKleY5sN7WN6kf+ChBFpnTku2y4IYH91EcRc= 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=MS5EINeh; arc=none smtp.client-ip=161.97.139.27 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 Received: from mail.andi.de1.cc ([2a02:c205:3004:2154::1]) by mail2.andi.de1.cc with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1sE8vA-008hAs-1Q; Mon, 03 Jun 2024 16:44:25 +0200 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=drKHUgwKkW2PbyIoNGASeFX7RLuVgwpvtctgYrIf9qo=; b=MS5EINehO8YEtY8F87HDRvWXR0 0XZcmZxSX1DTPUc5UT8M5At853EV28npV7+V3Gac4+oaSRZXN3JQXzjegKCk8czvD/3nr/PPu99w2 RWolAj6YftO8sIV75X5LVrwtcKrecI+SuIh8AD8BkuZkBo/v11elD0LS0nbmpvBd/Bu/2h8q4jdgH R9wDWMP3p06JxhYzJOnYB8uvXExCuM/U9EtAaa/vP5+VTrIzmCuPCZepz8PDla1o2bfyBRCTgniyk FtUBm8pGs3ga/s8WXsuCtPJXuXZpyBFYGo9PpcK2jjktS9SMto8n2lOwWnZ3nnVCMqNmjrAmqeaRE CRP1e4Qw==; Received: from p200300c20737c2001a3da2fffebfd33a.dip0.t-ipconnect.de ([2003:c2:737:c200: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.96) (envelope-from ) id 1sE8v8-002UfH-1z; Mon, 03 Jun 2024 16:44:23 +0200 Received: from andi by aktux with local (Exim 4.96) (envelope-from ) id 1sE8v9-009DAx-1y; Mon, 03 Jun 2024 16:44:23 +0200 From: Andreas Kemnade To: marcel@holtmann.org, luiz.dentz@gmail.com, johan@kernel.org, gregkh@linuxfoundation.org, jirislaby@kernel.org, andreas@kemnade.info, pmenzel@molgen.mpg.de, linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org, Adam Ford , Tony Lindgren , tomi.valkeinen@ideasonboard.com, =?UTF-8?q?P=C3=A9ter=20Ujfalusi?= , robh@kernel.org, hns@goldelico.com Subject: [PATCH v3 3/4] gnss: Add driver for AI2 protocol Date: Mon, 3 Jun 2024 16:43:59 +0200 Message-Id: <20240603144400.2195564-4-andreas@kemnade.info> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240603144400.2195564-1-andreas@kemnade.info> References: <20240603144400.2195564-1-andreas@kemnade.info> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 (ai2raw) resembling /dev/tigps provided by some TI vendor kernels. The fork used by the BT200 is at: http://epsonservice.goepson.com/downloads/VI-APS/BT200_kernel.tgz Signed-off-by: Andreas Kemnade Acked-by: Paul Menzel --- drivers/gnss/Kconfig | 13 ++ drivers/gnss/Makefile | 3 + drivers/gnss/ai2.c | 527 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 543 insertions(+) create mode 100644 drivers/gnss/ai2.c diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index d7fe265c28696..95fdab6e7ae94 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 + containing 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..0cc21b64b7c3f --- /dev/null +++ b/drivers/gnss/ai2.c @@ -0,0 +1,527 @@ +// 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 + * bluetooth part, but no signs 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); /* seen some 60ms response time here, so wait a bit */ + 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(50); + ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE); + if (ret) + goto err; + + msleep(100); + ret = ai2_config_nmea_reports(ai2dev, NMEA_MASK_ALL); + if (ret) + goto err; + + msleep(50); + ret = ai2_set_receiver_state(ai2dev, RECEIVER_STATE_ON); + if (ret) + goto err; + + msleep(50); + + 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(50); + ai2_set_receiver_state(ai2dev, RECEIVER_STATE_IDLE); + msleep(50); + ai2_set_receiver_state(ai2dev, RECEIVER_STATE_OFF); + msleep(200); /* seen some longer response time here, so wait */ + } + + 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; + } + + ai2dev->recv_esc = false; + 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