[RFC 2/2] bluetooth: Add initial support for BT chip over SMD

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

 



This patch adds support for Qualcomm chips which uses msm
shared memory driver as a transport layer.

This driver based on SMD driver found in  msm kernel branch.

Signed-off-by: Lukasz Rymanowski <lukasz.rymanowski@xxxxxxxxx>
---
 drivers/bluetooth/Kconfig   |   9 +
 drivers/bluetooth/Makefile  |   1 +
 drivers/bluetooth/hci_smd.c | 460 ++++++++++++++++++++++++++++++++++++++++++++
 include/net/bluetooth/hci.h |   1 +
 4 files changed, 471 insertions(+)
 create mode 100644 drivers/bluetooth/hci_smd.c

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 11a6104..f8a46c5 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -242,4 +242,13 @@ config BT_WILINK
 
 	  Say Y here to compile support for Texas Instrument's WiLink7 driver
 	  into the kernel or say M to compile it as module.
+
+config BT_HCISMD
+        tristate "Qualcomm HCI Shared Memory Driver"
+        help
+          This enables the Bluetooth driver for Qualcomm BT devices uses SMD interface.
+
+          Say Y here to compile support for Qualcomm over SMD driver into kernel or
+          say M to compile it as module (hci_smd)
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 9fe8a87..0666e60 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_BT_ATH3K)		+= ath3k.o
 obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_WILINK)		+= btwilink.o
+obj-$(CONFIG_BT_HCISMD)		+= hci_smd.o
 
 btmrvl-y			:= btmrvl_main.o
 btmrvl-$(CONFIG_DEBUG_FS)	+= btmrvl_debugfs.o
diff --git a/drivers/bluetooth/hci_smd.c b/drivers/bluetooth/hci_smd.c
new file mode 100644
index 0000000..9eb3675
--- /dev/null
+++ b/drivers/bluetooth/hci_smd.c
@@ -0,0 +1,460 @@
+
+/*
+ *
+ *  HCI_SMD (HCI Shared Memory Driver) is Qualcomm's Shared memory driver
+ *  for the HCI protocol.
+ *
+ *  Copyright (C) 2000-2001  Qualcomm Incorporated
+ *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@xxxxxxxxxxxx>
+ *  Copyright (C) 2004-2006  Marcel Holtmann <marcel@xxxxxxxxxxxx>
+ *  Copyright (C) 2011, Code Aurora Forum. All rights reserved.
+ *  Copyright (C) 2014, Intel Corporation. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 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/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/spinlock.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include <linux/smd.h>
+
+#define VERSION "0.1"
+
+#define SMD_CMD_CHANNEL "APPS_RIVA_BT_CMD"
+#define SMD_ACL_CHANNEL "APPS_RIVA_BT_ACL"
+
+#define SMD_APPS_RIVA_BT_CMD_READY 0x01
+#define SMD_APPS_RIVA_BT_ACL_READY 0x02
+#define SMD_READY ((SMD_APPS_RIVA_BT_CMD_READY) | (SMD_APPS_RIVA_BT_ACL_READY))
+
+struct rx_work {
+	struct work_struct work;
+	struct smd_data *smd;
+	struct hci_dev *hdev;
+	u8 pkt_type;
+};
+
+struct smd_channel_data {
+	struct hci_dev *hdev;
+	struct workqueue_struct *wq;
+	struct smd_data *smd;
+};
+
+struct hci_smd_data {
+	struct hci_dev *hdev;
+
+	struct smd_channel_data *smd_cmd;
+	struct smd_channel_data *smd_acl;
+	u8 ready_flags;
+	struct completion smd_ready;
+
+	/* Protects ready_flags */
+	spinlock_t flags_lock;
+};
+
+static struct hci_smd_data hs;
+
+static void hci_smd_channel_open(struct smd_channel_data *scd)
+{
+	const char *name = scd->smd->pdev->name;
+
+	spin_lock(&hs.flags_lock);
+	if (scd->hdev) {
+		set_bit(HCI_RUNNING, &scd->hdev->flags);
+		spin_unlock(&hs.flags_lock);
+		return;
+	}
+
+	if (!strncmp(name, SMD_CMD_CHANNEL, sizeof(SMD_CMD_CHANNEL)))
+		hs.ready_flags |= SMD_APPS_RIVA_BT_CMD_READY;
+
+	if (!strncmp(name, SMD_ACL_CHANNEL, sizeof(SMD_ACL_CHANNEL)))
+		hs.ready_flags |= SMD_APPS_RIVA_BT_ACL_READY;
+
+	if ((SMD_READY & hs.ready_flags) != SMD_READY) {
+		spin_unlock(&hs.flags_lock);
+		return;
+	}
+
+	spin_unlock(&hs.flags_lock);
+	complete_all(&hs.smd_ready);
+}
+
+static void hci_smd_channel_close(struct smd_channel_data *scd)
+{
+	if (!scd->hdev)
+		return;
+
+	clear_bit(HCI_RUNNING, &scd->hdev->flags);
+}
+
+static int hci_smd_open(struct hci_dev *hdev)
+{
+	BT_DBG("hdev %s, %p", hdev->name, hdev);
+
+	set_bit(HCI_RUNNING, &hdev->flags);
+	return 0;
+}
+
+static int hci_smd_close(struct hci_dev *hdev)
+{
+	BT_DBG("hdev %s %p", hdev->name, hdev);
+
+	clear_bit(HCI_RUNNING, &hdev->flags);
+	return 0;
+}
+
+static void hci_smd_rx_work(struct work_struct *work)
+{
+	struct rx_work *wk = container_of(work, struct rx_work, work);
+	u8 type = wk->pkt_type;
+	struct smd_data *smd = wk->smd;
+	struct hci_dev *hdev = wk->hdev;
+	struct sk_buff *skb;
+	int len;
+
+	BT_DBG("hdev %p, %02x", hdev, type);
+
+	/*It s save to free work here */
+	kfree(wk);
+
+	len = smd->ops.read_avail(smd);
+	if (len > HCI_MAX_FRAME_SIZE)
+		return;
+
+	while (len) {
+		int rc = 0;
+		skb = bt_skb_alloc(len, GFP_KERNEL);
+		if (!skb)
+			return;
+
+		rc = smd->ops.read(smd, skb_put(skb, len), len);
+		if (rc < len) {
+			kfree_skb(skb);
+			return;
+		}
+
+		skb->dev = (void *)hdev;
+		bt_cb(skb)->pkt_type = type;
+		skb_orphan(skb);
+
+		rc = hci_recv_frame(hdev, skb);
+		if (rc < 0) {
+			BT_ERR("Failed to pass skb to Bluetooth module");
+			kfree_skb(skb);
+			return;
+		}
+
+		len = smd->ops.read_avail(smd);
+		if (len > HCI_MAX_FRAME_SIZE)
+			return;
+	}
+}
+
+static int hci_smd_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct hci_smd_data *hs = hci_get_drvdata(hdev);
+	struct smd_data *smd;
+	int sent;
+
+	if (!hdev) {
+		BT_ERR("Frame for unknown HCI device (hdev=NULL)");
+		return -ENODEV;
+	}
+
+	if (!test_bit(HCI_RUNNING, &(hdev->flags)))
+		return -EBUSY;
+
+	switch (bt_cb(skb)->pkt_type) {
+	case HCI_COMMAND_PKT:
+		smd  = hs->smd_cmd->smd;
+		sent = smd->ops.write(smd, skb->data, skb->len);
+		break;
+	case HCI_ACLDATA_PKT:
+	case HCI_SCODATA_PKT:
+		smd = hs->smd_acl->smd;
+		sent = smd->ops.write(smd, skb->data, skb->len);
+		break;
+	default:
+		BT_ERR("Unknown package");
+		kfree_skb(skb);
+		return -EPROTO;
+	}
+
+	kfree_skb(skb);
+
+	if (sent < 0) {
+		BT_ERR("Failed to send all data");
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+static struct rx_work *alloc_rx_work(u8 pkt_type, struct smd_data *smd,
+							struct hci_dev *hdev)
+{
+	struct rx_work *w = kmalloc(sizeof(*w), GFP_ATOMIC);
+
+	if (!w) {
+		BT_ERR("Could not allocate work");
+		return NULL;
+	}
+
+	INIT_WORK(&w->work, hci_smd_rx_work);
+	w->pkt_type = pkt_type;
+	w->smd = smd;
+	w->hdev = hdev;
+
+	return w;
+}
+
+static void hci_smd_notify(struct platform_device *pdev,
+					unsigned int event, u8 pkt_type)
+{
+	struct smd_channel_data *scd = dev_get_drvdata(&pdev->dev);
+	struct hci_dev *hdev = scd->hdev;
+	struct rx_work *w;
+
+	if (!scd || !scd->smd) {
+		BT_ERR("SMD channel data not avaiable");
+		return;
+	}
+
+	switch (event) {
+	case SMD_EVENT_DATA:
+		w = alloc_rx_work(pkt_type, scd->smd, hdev);
+		if (w && hdev)
+			queue_work(scd->wq, &w->work);
+		else
+			BT_ERR("Read failed hdev:%p, work:%p ", hdev, w);
+		break;
+	case SMD_EVENT_OPEN:
+		hci_smd_channel_open(scd);
+		break;
+	case SMD_EVENT_CLOSE:
+		hci_smd_channel_close(scd);
+		break;
+	default:
+		break;
+	}
+}
+
+static void hci_smd_notify_cmd(struct platform_device *pdev,
+							unsigned int event)
+{
+	hci_smd_notify(pdev, event, HCI_EVENT_PKT);
+}
+
+static void hci_smd_notify_data(struct platform_device *pdev,
+							unsigned int event)
+{
+	hci_smd_notify(pdev, event, HCI_ACLDATA_PKT);
+}
+
+static int hci_smd_register(void)
+{
+	struct hci_dev *hdev;
+	int err;
+
+	BT_DBG("hci_smd_register");
+
+	/*
+	* Lets use two different worqueues for Event and ACL data so we make
+	* sure that Event will never be blocked by ACL data.
+	*/
+	hs.smd_cmd->wq = alloc_workqueue("smd_event", WQ_HIGHPRI |
+					WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+	if (!hs.smd_cmd->wq) {
+		BT_ERR("Error allocating event workqueue");
+		err = -ENOMEM;
+		goto close_smd;
+	}
+
+	hs.smd_acl->wq = alloc_workqueue("data_event", WQ_HIGHPRI |
+					WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+	if (!hs.smd_acl->wq) {
+		BT_ERR("Error allocating data workqueue");
+		destroy_workqueue(hs.smd_cmd->wq);
+		err = -ENOMEM;
+		goto close_smd;
+	}
+
+	/* Initialize and register HCI device */
+	hdev = hci_alloc_dev();
+	if (!hdev) {
+		BT_ERR("Error allocating HCI dev");
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	hdev->bus = HCI_SMD;
+	hci_set_drvdata(hdev, &hs);
+
+	hdev->open	= hci_smd_open;
+	hdev->close	= hci_smd_close;
+	hdev->send	= hci_smd_send_frame;
+
+	hs.smd_cmd->hdev = hdev;
+	hs.smd_acl->hdev = hdev;
+
+	err = hci_register_dev(hdev);
+	if (!err)
+		return 0;
+
+	BT_ERR("Can't register HCI device");
+	hci_free_dev(hdev);
+
+cleanup:
+	destroy_workqueue(hs.smd_cmd->wq);
+	destroy_workqueue(hs.smd_acl->wq);
+close_smd:
+	hs.smd_cmd->smd->ops.close(hs.smd_cmd->smd);
+	hs.smd_acl->smd->ops.close(hs.smd_acl->smd);
+
+	return err;
+}
+
+static int smd_cmd_channel_probe(struct platform_device *pdev)
+{
+	struct smd_data *smd = dev_get_platdata(&pdev->dev);
+	struct smd_channel_data *scd = kzalloc(sizeof(*scd), GFP_KERNEL);
+	int err;
+
+	scd->smd = smd;
+	hs.smd_cmd = scd;
+
+	dev_set_drvdata(&pdev->dev, scd);
+
+	err = smd->ops.open(smd, hci_smd_notify_cmd);
+	if (err < 0) {
+		BT_ERR("Can not open %s", SMD_CMD_CHANNEL);
+		return err;
+	}
+
+	return 0;
+}
+
+static int smd_data_channel_probe(struct platform_device *pdev)
+{
+	struct smd_data *smd = dev_get_platdata(&pdev->dev);
+	struct smd_channel_data *scd = kzalloc(sizeof(*scd), GFP_KERNEL);
+	int err;
+
+	scd->smd = smd;
+	hs.smd_acl = scd;
+
+	dev_set_drvdata(&pdev->dev, scd);
+
+	err = smd->ops.open(smd, hci_smd_notify_data);
+	if (err < 0) {
+		BT_ERR("Can not open %s", SMD_ACL_CHANNEL);
+		return err;
+	}
+
+	return 0;
+}
+
+
+static int smd_channel_remove(struct platform_device *pdev)
+{
+	struct smd_data *smd = dev_get_platdata(&pdev->dev);
+	return smd->ops.close(smd);
+}
+
+static struct platform_driver cmd_drv = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = SMD_CMD_CHANNEL,
+	},
+	.probe = smd_cmd_channel_probe,
+	.remove = smd_channel_remove,
+
+};
+
+static struct platform_driver acl_drv = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = SMD_ACL_CHANNEL,
+	},
+	.probe = smd_data_channel_probe,
+	.remove = smd_channel_remove,
+};
+
+static int __init hci_smd_init(void)
+{
+	int err;
+
+	BT_INFO("hci smd driver ver %s", VERSION);
+
+	memset(&hs, 0, sizeof(hs));
+
+	spin_lock_init(&hs.flags_lock);
+	init_completion(&hs.smd_ready);
+
+	/*
+	* SMD channels are represented by platform devices. We need them two
+	* for BT operations. Channel for BT CMD/EVENT traffic and BT ACL DATA
+	* traffic.
+	*/
+	err = platform_driver_register(&cmd_drv);
+	if (err < 0) {
+		BT_ERR("Failed to register drv: %s err: %d",
+						cmd_drv.driver.name, err);
+		return err;
+	}
+
+	err = platform_driver_register(&acl_drv);
+	if (err < 0) {
+		BT_ERR("Failed to register drv: %s, err: %d",
+						acl_drv.driver.name, err);
+		platform_driver_unregister(&cmd_drv);
+		return err;
+	}
+
+	/* Let's wait until SMD channels are ready */
+	err = wait_for_completion_killable_timeout(&hs.smd_ready,
+		msecs_to_jiffies(5000));
+	if (err <= 0)
+		return err;
+
+	return hci_smd_register();
+}
+
+static void __exit hci_smd_exit(void)
+{
+	kfree(hs.smd_cmd);
+	kfree(hs.smd_acl);
+
+	platform_driver_unregister(&cmd_drv);
+	platform_driver_unregister(&acl_drv);
+}
+
+module_init(hci_smd_init);
+module_exit(hci_smd_exit);
+
+MODULE_AUTHOR("Lukasz Rymanowski <lukasz.rymanowski@xxxxxxxxx>");
+MODULE_AUTHOR("Ankur Nandwani <ankurn@xxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Bluetooth SMD driver ver " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 352d3d7..149b06a 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
-- 
1.8.4

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




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux