Some of these chips have GNSS support. GNSS support is available through channel 9 whilst FM is through channel 8. Add support for it. A driver providing a /dev/tigps device is found in some vendor-kernels. The GNSS device provided by this patch seems to be compatible. Signed-off-by: Andreas Kemnade <andreas@xxxxxxxxxxxx> --- drivers/bluetooth/hci_ll.c | 154 ++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c index 4a0b5c3160c2b..718dab0f529a7 100644 --- a/drivers/bluetooth/hci_ll.c +++ b/drivers/bluetooth/hci_ll.c @@ -39,6 +39,7 @@ #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> +#include <linux/gnss.h> #include <linux/gpio/consumer.h> #include <linux/nvmem-consumer.h> @@ -68,6 +69,10 @@ struct ll_device { struct gpio_desc *enable_gpio; struct clk *ext_clk; bdaddr_t bdaddr; + + struct mutex gdev_mutex; + bool gdev_open; + struct gnss_device *gdev; }; struct ll_struct { @@ -78,6 +83,20 @@ struct ll_struct { struct sk_buff_head tx_wait_q; /* HCILL wait queue */ }; +/* Channel-9 details for GPS */ +#define GPS_CH9_PKT_HDR_SIZE 4 +#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 + +struct gnssdrv_event_hdr { + uint8_t opcode; + uint16_t plen; +} __packed; + + +static int 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 +430,13 @@ static int ll_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) .lsize = 0, \ .maxlen = 0 +#define LL_RECV_GNSS \ + .type = GPS_CH9_PKT_NUMBER, \ + .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 +445,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 = gnss_recv_frame }, }; /* Recv data */ @@ -682,12 +709,124 @@ static int ll_setup(struct hci_uart *hu) static const struct hci_uart_proto llp; +static int gnss_lldev_open(struct gnss_device *gdev) +{ + struct ll_device *lldev = gnss_get_drvdata(gdev); + + mutex_lock(&lldev->gdev_mutex); + lldev->gdev_open = true; + mutex_unlock(&lldev->gdev_mutex); + + return 0; +} + +static void gnss_lldev_close(struct gnss_device *gdev) +{ + struct ll_device *lldev = gnss_get_drvdata(gdev); + + mutex_lock(&lldev->gdev_mutex); + lldev->gdev_open = false; + mutex_unlock(&lldev->gdev_mutex); +} + +static int gnss_lldev_write_raw(struct gnss_device *gdev, + const unsigned char *buf, size_t count) +{ + struct ll_device *lldev = gnss_get_drvdata(gdev); + int err = 0; + struct sk_buff *skb = NULL; + struct gnssdrv_event_hdr gnssdrv_hdr = { GPS_CH9_OP_WRITE, 0x0000 }; + + /* allocate packet */ + skb = bt_skb_alloc(sizeof(gnssdrv_hdr) + count, GFP_ATOMIC); + if (!skb) { + BT_ERR("cannot allocate memory for HCILL packet"); + err = -ENOMEM; + goto out; + } + + /* GPS channel */ + + gnssdrv_hdr.plen = count; + hci_skb_pkt_type(skb) = GPS_CH9_PKT_NUMBER; + skb_put_data(skb, &gnssdrv_hdr, sizeof(gnssdrv_hdr)); + skb_put_data(skb, buf, count); + + lldev->hu.hdev->send(lldev->hu.hdev, skb); + + /* TODO: wait on completion? */ + return count; +out: + return err; +} + +static const struct gnss_operations gnss_lldev_ops = { + .open = gnss_lldev_open, + .close = gnss_lldev_close, + .write_raw = gnss_lldev_write_raw, +}; + +static int 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 (!IS_ENABLED(CONFIG_GNSS)) + return 0; + + if (!lldev->gdev) + return 0; + + if (hci_skb_pkt_type(skb) == GPS_CH9_PKT_NUMBER) { + struct gnssdrv_event_hdr *gnss_hdr = + (struct gnssdrv_event_hdr *)skb->data; + void *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(&lldev->gdev_mutex); + if (lldev->gdev_open) + gnss_insert_raw(lldev->gdev, data, skb->len); + mutex_unlock(&lldev->gdev_mutex); + } + } + kfree_skb(skb); + + return 0; +} + +static int ll_gnss_register(struct ll_device *lldev) +{ + struct gnss_device *gdev; + int ret; + + gdev = gnss_allocate_device(&lldev->serdev->dev); + if (!gdev) + return -ENOMEM; + + gdev->ops = &gnss_lldev_ops; + gdev->type = GNSS_TYPE_AI2; + gnss_set_drvdata(gdev, lldev); + mutex_init(&lldev->gdev_mutex); + + ret = gnss_register_device(gdev); + if (ret == 0) { + lldev->gdev = gdev; + return 0; + } + gnss_put_device(gdev); + return ret; +} + static int hci_ti_probe(struct serdev_device *serdev) { struct hci_uart *hu; struct ll_device *lldev; struct nvmem_cell *bdaddr_cell; u32 max_speed = 3000000; + int ret; lldev = devm_kzalloc(&serdev->dev, sizeof(struct ll_device), GFP_KERNEL); if (!lldev) @@ -756,14 +895,27 @@ static int hci_ti_probe(struct serdev_device *serdev) kfree(bdaddr); } - return hci_uart_register_device(hu, &llp); + ret = hci_uart_register_device(hu, &llp); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_GNSS) && + strstr(of_node_full_name(serdev->dev.of_node), "gnss")) + ll_gnss_register(lldev); + + return 0; } + static void hci_ti_remove(struct serdev_device *serdev) { struct ll_device *lldev = serdev_device_get_drvdata(serdev); hci_uart_unregister_device(&lldev->hu); + if (IS_ENABLED(CONFIG_GNSS) && lldev->gdev) { + gnss_deregister_device(lldev->gdev); + gnss_put_device(lldev->gdev); + } } static const struct of_device_id hci_ti_of_match[] = { -- 2.39.2