[PATCH] Bluetooth: hci_smd: Qualcomm WCNSS HCI driver

From: bjorn.andersson
Date: Wed Sep 30 2015 - 19:03:14 EST


From: Bjorn Andersson <bjorn.andersson@xxxxxxxxxxxxxx>

The Qualcomm WCNSS chip provides two SMD channels to the BT core; one
for command and one for event packets. This driver exposes the two
channels as a hci device.

Signed-off-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxxxxxx>
---
drivers/bluetooth/Kconfig | 11 +++
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/hci_smd.c | 222 ++++++++++++++++++++++++++++++++++++++++++++
include/net/bluetooth/hci.h | 1 +
4 files changed, 235 insertions(+)
create mode 100644 drivers/bluetooth/hci_smd.c

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 3d480d8c6111..1a2658f373b5 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -62,6 +62,17 @@ config BT_HCIBTSDIO
Say Y here to compile support for Bluetooth SDIO devices into the
kernel or say M to compile it as module (btsdio).

+config BT_HCISMD
+ tristate "HCI Qualcomm SMD driver"
+ depends on QCOM_SMD
+ help
+ Qualcomm SMD HCI driver.
+ This driver is used to bridge HCI data onto the shared memory
+ channels to the WCNSS core.
+
+ Say Y here to compile support for HCI over Qualcomm SMD into the
+ kernelor say M to compile as a module.
+
config BT_HCIUART
tristate "HCI UART driver"
depends on TTY
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 07c9cf381e5a..43c7dc8641ff 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o

obj-$(CONFIG_BT_HCIBTUSB) += btusb.o
obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o
+obj-$(CONFIG_BT_HCISMD) += hci_smd.o

obj-$(CONFIG_BT_INTEL) += btintel.o
obj-$(CONFIG_BT_ATH3K) += ath3k.o
diff --git a/drivers/bluetooth/hci_smd.c b/drivers/bluetooth/hci_smd.c
new file mode 100644
index 000000000000..e5748da2f902
--- /dev/null
+++ b/drivers/bluetooth/hci_smd.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2015, Sony Mobile Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/smd.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/hci.h>
+
+static struct {
+ struct qcom_smd_channel *acl_channel;
+ struct qcom_smd_channel *cmd_channel;
+
+ struct hci_dev *hci;
+} smd_hci;
+
+static int smd_hci_recv(unsigned type, const void *data, size_t count)
+{
+ struct sk_buff *skb;
+ void *buf;
+ int ret;
+
+ skb = bt_skb_alloc(count, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ buf = skb_put(skb, count);
+ memcpy_fromio(buf, data, count);
+
+ skb->dev = (void *)smd_hci.hci;
+ bt_cb(skb)->pkt_type = type;
+ skb_orphan(skb);
+
+ ret = hci_recv_frame(smd_hci.hci, skb);
+ if (ret < 0)
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static int smd_hci_acl_callback(struct qcom_smd_device *qsdev,
+ const void *data,
+ size_t count)
+{
+ return smd_hci_recv(HCI_ACLDATA_PKT, data, count);
+}
+
+static int smd_hci_cmd_callback(struct qcom_smd_device *qsdev,
+ const void *data,
+ size_t count)
+{
+ return smd_hci_recv(HCI_EVENT_PKT, data, count);
+}
+
+static int smd_hci_send(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ int ret;
+
+ switch (bt_cb(skb)->pkt_type) {
+ case HCI_ACLDATA_PKT:
+ case HCI_SCODATA_PKT:
+ ret = qcom_smd_send(smd_hci.acl_channel, skb->data, skb->len);
+ break;
+ case HCI_COMMAND_PKT:
+ ret = qcom_smd_send(smd_hci.cmd_channel, skb->data, skb->len);
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static int smd_hci_open(struct hci_dev *hci)
+{
+ return 0;
+}
+
+static int smd_hci_close(struct hci_dev *hci)
+{
+ return 0;
+}
+
+static int smd_hci_set_bdaddr(struct hci_dev *hci,
+ const bdaddr_t *bdaddr)
+{
+ u8 buf[12];
+
+ buf[0] = 0x0b;
+ buf[1] = 0xfc;
+ buf[2] = 0x9;
+ buf[3] = 0x1;
+ buf[4] = 0x2;
+ buf[5] = sizeof(bdaddr_t);
+ memcpy(buf + 6, bdaddr, sizeof(bdaddr_t));
+
+ return qcom_smd_send(smd_hci.cmd_channel, buf, sizeof(buf));
+}
+
+static int smd_hci_register(void)
+{
+ struct hci_dev *hci;
+ int ret;
+
+ if (smd_hci.hci)
+ return 0;
+
+ /* Wait for both channels to probe before registering */
+ if (!smd_hci.acl_channel || !smd_hci.cmd_channel)
+ return 0;
+
+ hci = hci_alloc_dev();
+ if (!hci)
+ return -ENOMEM;
+
+ hci->bus = HCI_SMD;
+ hci->open = smd_hci_open;
+ hci->close = smd_hci_close;
+ hci->send = smd_hci_send;
+ hci->set_bdaddr = smd_hci_set_bdaddr;
+
+ ret = hci_register_dev(hci);
+ if (ret < 0) {
+ hci_free_dev(hci);
+ return ret;
+ }
+
+ smd_hci.hci = hci;
+
+ return 0;
+}
+
+static void smd_hci_unregister(void)
+{
+ /* Only unregister on the first remove call */
+ if (!smd_hci.hci)
+ return;
+
+ hci_unregister_dev(smd_hci.hci);
+ hci_free_dev(smd_hci.hci);
+ smd_hci.hci = NULL;
+}
+
+static int smd_hci_acl_probe(struct qcom_smd_device *sdev)
+{
+ smd_hci.acl_channel = sdev->channel;
+ smd_hci_register();
+
+ return 0;
+}
+
+static int smd_hci_cmd_probe(struct qcom_smd_device *sdev)
+{
+ smd_hci.cmd_channel = sdev->channel;
+ smd_hci_register();
+
+ return 0;
+}
+
+static void smd_hci_acl_remove(struct qcom_smd_device *sdev)
+{
+ smd_hci.acl_channel = NULL;
+ smd_hci_unregister();
+}
+
+static void smd_hci_cmd_remove(struct qcom_smd_device *sdev)
+{
+ smd_hci.cmd_channel = NULL;
+ smd_hci_unregister();
+}
+
+static const struct qcom_smd_id smd_hci_acl_match[] = {
+ { .name = "APPS_RIVA_BT_ACL" },
+ {}
+};
+
+static const struct qcom_smd_id smd_hci_cmd_match[] = {
+ { .name = "APPS_RIVA_BT_CMD" },
+ {}
+};
+
+static struct qcom_smd_driver smd_hci_acl_driver = {
+ .probe = smd_hci_acl_probe,
+ .remove = smd_hci_acl_remove,
+ .callback = smd_hci_acl_callback,
+ .smd_match_table = smd_hci_acl_match,
+ .driver = {
+ .name = "qcom_smd_hci_acl",
+ .owner = THIS_MODULE,
+ },
+};
+
+static struct qcom_smd_driver smd_hci_cmd_driver = {
+ .probe = smd_hci_cmd_probe,
+ .remove = smd_hci_cmd_remove,
+ .callback = smd_hci_cmd_callback,
+ .smd_match_table = smd_hci_cmd_match,
+ .driver = {
+ .name = "qcom_smd_hci_cmd",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_qcom_smd_driver(smd_hci_acl_driver);
+module_qcom_smd_driver(smd_hci_cmd_driver);
+
+MODULE_DESCRIPTION("Qualcomm SMD HCI driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 7ca6690355ea..ee5b2dd922f6 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -58,6 +58,7 @@
#define HCI_RS232 4
#define HCI_PCI 5
#define HCI_SDIO 6
+#define HCI_SMD 7

/* HCI controller types */
#define HCI_BREDR 0x00
--
2.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/