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

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

 



Hi,

On 7 February 2014 12:35, Lukasz Rymanowski <lukasz.rymanowski@xxxxxxxxx> 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 <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);

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 <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
> --

Adding linux-arm-msm group for comments on SMD API

BR
Lukasz
> 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