Hello,
This is a try (long shoot) to upstream hci shared memory driver (hci_smd)
which is used on Qualcomm platforms and BT chips.
To make it build with upstream tree I had to introduce some simple SMD API.
The idea here is that since SMD channel is represented by platform device
(as it is done in mach-msm),
then platfrom_data contains smd_data which contains API for that channel.
Writing this SMD API I was inspired with MSM-SMD and since I'm not an expert
in this area I'm asking for comments here.
Maybe instead of SMD API I made, I should expose functions like smd_open(), smd_write()
etc. something similar how sdio does, and just deliver to upstream dummy device
implementing this SMD API?
Other options I was considering is to implement module inside mach-msm which
would handle registering SMD BT Channels and when it is done, it would register
new platform device like "smd-bt". Then I could move smd.h to some bluetooth includes
(althought don't now where at the moment) and hci_smd would register driver for "smd-bt"
In this case I would also could rid of one static variable I have now in hci_smd
Comments on those options are welcome.
Anyway, Hci_smd is based on one of the older version of this driver found in msm kernel
branch, so there are no wakelocks as in new version and also workqueues are used
instead of takslet.
Since SMD expose two channels, one for CMD/EVENT and one for ACL Data I decide to
do separate worqueues for this. This is to make sure that ACL data never blocks EVENT
packages
Lukasz Rymanowski (2):
Add basic API for shared memory driver
bluetooth: Add initial support for BT chip over SMD
drivers/bluetooth/Kconfig | 9 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/hci_smd.c | 461 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/smd.h | 68 +++++++
include/net/bluetooth/hci.h | 1 +
5 files changed, 540 insertions(+)
create mode 100644 drivers/bluetooth/hci_smd.c
create mode 100644 include/linux/smd.h
--
1.8.4
Hi,
On 11 February 2014 21:00, Kumar Gala <[email protected]> wrote:
>
> On Feb 11, 2014, at 10:23 AM, Lukasz Rymanowski <[email protected]> wrote:
>
>> Hi
>>
>> On 7 February 2014 12:35, Lukasz Rymanowski <[email protected]> wrote:
>>> Hello,
>>>
>>> This is a try (long shoot) to upstream hci shared memory driver (hci_smd)
>>> which is used on Qualcomm platforms and BT chips.
>>>
>>> To make it build with upstream tree I had to introduce some simple SMD API.
>>> The idea here is that since SMD channel is represented by platform device
>>> (as it is done in mach-msm),
>>> then platfrom_data contains smd_data which contains API for that channel.
>>>
>>> Writing this SMD API I was inspired with MSM-SMD and since I'm not an expert
>>> in this area I'm asking for comments here.
>>>
>>> Maybe instead of SMD API I made, I should expose functions like smd_open(), smd_write()
>>> etc. something similar how sdio does, and just deliver to upstream dummy device
>>> implementing this SMD API?
>>>
>>> Other options I was considering is to implement module inside mach-msm which
>>> would handle registering SMD BT Channels and when it is done, it would register
>>> new platform device like "smd-bt". Then I could move smd.h to some bluetooth includes
>>> (althought don't now where at the moment) and hci_smd would register driver for "smd-bt"
>>> In this case I would also could rid of one static variable I have now in hci_smd
>>>
>>> Comments on those options are welcome.
>>>
>>> Anyway, Hci_smd is based on one of the older version of this driver found in msm kernel
>>> branch, so there are no wakelocks as in new version and also workqueues are used
>>> instead of takslet.
>>>
>>> Since SMD expose two channels, one for CMD/EVENT and one for ACL Data I decide to
>>> do separate worqueues for this. This is to make sure that ACL data never blocks EVENT
>>> packages
>>>
>>
>> Adding linux-arm-msm group for comments on SMD API.
>>
>> BR
>> Lukasz
>
> What chip/SoC/board are you trying to get bluetooth support on?
>
I'm already running it on Nexus 4 and 7, but for this I have also more
changes in mach-msm (smd_tty.c, smd.c) on top of msm kernel branch
for android.
\Lukasz
> - k
>
> --
> Employee of Qualcomm Innovation Center, Inc.
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
>
On Feb 11, 2014, at 10:23 AM, Lukasz Rymanowski <[email protected]> wrote:
> Hi
>
> On 7 February 2014 12:35, Lukasz Rymanowski <[email protected]> wrote:
>> Hello,
>>
>> This is a try (long shoot) to upstream hci shared memory driver (hci_smd)
>> which is used on Qualcomm platforms and BT chips.
>>
>> To make it build with upstream tree I had to introduce some simple SMD API.
>> The idea here is that since SMD channel is represented by platform device
>> (as it is done in mach-msm),
>> then platfrom_data contains smd_data which contains API for that channel.
>>
>> Writing this SMD API I was inspired with MSM-SMD and since I'm not an expert
>> in this area I'm asking for comments here.
>>
>> Maybe instead of SMD API I made, I should expose functions like smd_open(), smd_write()
>> etc. something similar how sdio does, and just deliver to upstream dummy device
>> implementing this SMD API?
>>
>> Other options I was considering is to implement module inside mach-msm which
>> would handle registering SMD BT Channels and when it is done, it would register
>> new platform device like "smd-bt". Then I could move smd.h to some bluetooth includes
>> (althought don't now where at the moment) and hci_smd would register driver for "smd-bt"
>> In this case I would also could rid of one static variable I have now in hci_smd
>>
>> Comments on those options are welcome.
>>
>> Anyway, Hci_smd is based on one of the older version of this driver found in msm kernel
>> branch, so there are no wakelocks as in new version and also workqueues are used
>> instead of takslet.
>>
>> Since SMD expose two channels, one for CMD/EVENT and one for ACL Data I decide to
>> do separate worqueues for this. This is to make sure that ACL data never blocks EVENT
>> packages
>>
>
> Adding linux-arm-msm group for comments on SMD API.
>
> BR
> Lukasz
What chip/SoC/board are you trying to get bluetooth support on?
- k
--
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation
Hi,
On 7 February 2014 12:35, Lukasz Rymanowski <[email protected]> wrote:
> 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 <[email protected]>
> ---
> 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 <[email protected]>
> + * Copyright (C) 2004-2006 Marcel Holtmann <[email protected]>
> + * 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);
This is wrong. need to remove it.
> + 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 <[email protected]>");
> +MODULE_AUTHOR("Ankur Nandwani <[email protected]>");
> +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
> --
Adding linux-arm-msm group for comments on SMD API
BR
Lukasz
> 1.8.4
>
Hi
On 7 February 2014 12:35, Lukasz Rymanowski <[email protected]> wrote:
> This patch adds simple API to shared memory driver based on msm-smd.h
> This is required in order to add support for support Qualcomm BT chips
> via SMD
>
> Signed-off-by: Lukasz Rymanowski <[email protected]>
> ---
> include/linux/smd.h | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 69 insertions(+)
> create mode 100644 include/linux/smd.h
>
> diff --git a/include/linux/smd.h b/include/linux/smd.h
> new file mode 100644
> index 0000000..58de0d4
> --- /dev/null
> +++ b/include/linux/smd.h
> @@ -0,0 +1,69 @@
> +/*
> +* smd.h - API for Shared Memory Driver
> +*
> +* Copyright (c) 2014 Intel Corporation
> +*
> +* 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.
> +*/
> +
> +#ifndef _SMD_H_
> +#define _SMD_H_
> +
> +#include <linux/platform_device.h>
> +
> +/**
> +* Events from smd device.
> +*/
> +#define SMD_EVENT_DATA 1
> +#define SMD_EVENT_OPEN 2
> +#define SMD_EVENT_CLOSE 3
> +
> +struct smd_channel;
> +struct smd_notify_data;
> +struct smd_data;
> +
> +/**
> +* struct smd_ops - specific operation for smd channel
> +*
> +* @open: Open SMD channel. It should return zero once device is opened.
> +* On open there is need to pass notification cb which is used
> +* for notifications form SMD device.
> +* @close Close SMD channel
> +* @write: Write data to SMD channel. It should return number of written
> +* bytes or negative value in case of error.
> +* @read: Read data from SMD channel.It should return number of written
> +* bytes or negative value in case of error.
> +*@read_avail: Returns number of bytes ready to be read. This should be called
> +* before read in order to allocate enought skb
> +*
> +*/
> +struct smd_ops {
> + int (*open)(struct smd_data *s,
> + void (*notify)(struct platform_device *pdev,
> + unsigned int event));
> + int (*close)(struct smd_data *s);
> + int (*write)(struct smd_data *s, const void *data, int len);
> + int (*read)(struct smd_data *s, void *buf, int len);
> + int (*read_avail)(struct smd_data *s);
> +};
> +/**
> +* struct smd_data - smd device data for shared memory channel
> +*
> +* @pdev: Platform device for given smd channel.
> +* @ops: SMD specific operations for this smd channel.
> +* @ch: SMD channel which is used by SMD device. Should not be
> +* used by the driver
> +* @notifier: SMD notifier data. Shall not be used by driver.
> +*
> +* smd_data are initialized by the platform and are available for driver
> +* in platform_data.
> +*/
> +struct smd_data {
> + struct platform_device *pdev;
> + struct smd_ops ops;
> + struct smd_channel *ch;
> + struct smd_notify_data *notify_data;
> +};
> +#endif
> --
Adding linux-arm-msm group for comments on SMD API
BR
Lukasz
> 1.8.4
>
Hi
On 7 February 2014 12:35, Lukasz Rymanowski <[email protected]> wrote:
> Hello,
>
> This is a try (long shoot) to upstream hci shared memory driver (hci_smd)
> which is used on Qualcomm platforms and BT chips.
>
> To make it build with upstream tree I had to introduce some simple SMD API.
> The idea here is that since SMD channel is represented by platform device
> (as it is done in mach-msm),
> then platfrom_data contains smd_data which contains API for that channel.
>
> Writing this SMD API I was inspired with MSM-SMD and since I'm not an expert
> in this area I'm asking for comments here.
>
> Maybe instead of SMD API I made, I should expose functions like smd_open(), smd_write()
> etc. something similar how sdio does, and just deliver to upstream dummy device
> implementing this SMD API?
>
> Other options I was considering is to implement module inside mach-msm which
> would handle registering SMD BT Channels and when it is done, it would register
> new platform device like "smd-bt". Then I could move smd.h to some bluetooth includes
> (althought don't now where at the moment) and hci_smd would register driver for "smd-bt"
> In this case I would also could rid of one static variable I have now in hci_smd
>
> Comments on those options are welcome.
>
> Anyway, Hci_smd is based on one of the older version of this driver found in msm kernel
> branch, so there are no wakelocks as in new version and also workqueues are used
> instead of takslet.
>
> Since SMD expose two channels, one for CMD/EVENT and one for ACL Data I decide to
> do separate worqueues for this. This is to make sure that ACL data never blocks EVENT
> packages
>
Adding linux-arm-msm group for comments on SMD API.
BR
Lukasz
> Lukasz Rymanowski (2):
> Add basic API for shared memory driver
> bluetooth: Add initial support for BT chip over SMD
>
> drivers/bluetooth/Kconfig | 9 +
> drivers/bluetooth/Makefile | 1 +
> drivers/bluetooth/hci_smd.c | 461 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/smd.h | 68 +++++++
> include/net/bluetooth/hci.h | 1 +
> 5 files changed, 540 insertions(+)
> create mode 100644 drivers/bluetooth/hci_smd.c
> create mode 100644 include/linux/smd.h
>
> --
> 1.8.4
>
This patch adds simple API to shared memory driver based on msm-smd.h
This is required in order to add support for support Qualcomm BT chips
via SMD
Signed-off-by: Lukasz Rymanowski <[email protected]>
---
include/linux/smd.h | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 include/linux/smd.h
diff --git a/include/linux/smd.h b/include/linux/smd.h
new file mode 100644
index 0000000..58de0d4
--- /dev/null
+++ b/include/linux/smd.h
@@ -0,0 +1,69 @@
+/*
+* smd.h - API for Shared Memory Driver
+*
+* Copyright (c) 2014 Intel Corporation
+*
+* 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.
+*/
+
+#ifndef _SMD_H_
+#define _SMD_H_
+
+#include <linux/platform_device.h>
+
+/**
+* Events from smd device.
+*/
+#define SMD_EVENT_DATA 1
+#define SMD_EVENT_OPEN 2
+#define SMD_EVENT_CLOSE 3
+
+struct smd_channel;
+struct smd_notify_data;
+struct smd_data;
+
+/**
+* struct smd_ops - specific operation for smd channel
+*
+* @open: Open SMD channel. It should return zero once device is opened.
+* On open there is need to pass notification cb which is used
+* for notifications form SMD device.
+* @close Close SMD channel
+* @write: Write data to SMD channel. It should return number of written
+* bytes or negative value in case of error.
+* @read: Read data from SMD channel.It should return number of written
+* bytes or negative value in case of error.
+*@read_avail: Returns number of bytes ready to be read. This should be called
+* before read in order to allocate enought skb
+*
+*/
+struct smd_ops {
+ int (*open)(struct smd_data *s,
+ void (*notify)(struct platform_device *pdev,
+ unsigned int event));
+ int (*close)(struct smd_data *s);
+ int (*write)(struct smd_data *s, const void *data, int len);
+ int (*read)(struct smd_data *s, void *buf, int len);
+ int (*read_avail)(struct smd_data *s);
+};
+/**
+* struct smd_data - smd device data for shared memory channel
+*
+* @pdev: Platform device for given smd channel.
+* @ops: SMD specific operations for this smd channel.
+* @ch: SMD channel which is used by SMD device. Should not be
+* used by the driver
+* @notifier: SMD notifier data. Shall not be used by driver.
+*
+* smd_data are initialized by the platform and are available for driver
+* in platform_data.
+*/
+struct smd_data {
+ struct platform_device *pdev;
+ struct smd_ops ops;
+ struct smd_channel *ch;
+ struct smd_notify_data *notify_data;
+};
+#endif
--
1.8.4
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 <[email protected]>
---
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 <[email protected]>
+ * Copyright (C) 2004-2006 Marcel Holtmann <[email protected]>
+ * 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 <[email protected]>");
+MODULE_AUTHOR("Ankur Nandwani <[email protected]>");
+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