[PATCH] Bluetooth: hci_smd: Qualcomm WCNSS HCI driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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-arm-msm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux