2015-05-13 21:50:19

by Ilya Faenson

[permalink] [raw]
Subject: [RFC v3 0/4] Broadcom Bluetooth UART device driver

The next version of the driver changed as per Marcel's comments.

Ilya Faenson (4):
Broadcom Bluetooth UART Device Tree bindings
Broadcom Bluetooth UART Platform Driver
Broadcom Bluetooth protocol UART support
BlueZ Broadcom UART Protocol

.../devicetree/bindings/net/bluetooth/btbcm.txt | 54 ++
drivers/bluetooth/Kconfig | 9 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btbcm.c | 155 ++++-
drivers/bluetooth/btbcm.h | 13 +
drivers/bluetooth/btbcm_uart.c | 705 +++++++++++++++++++++
drivers/bluetooth/btbcm_uart.h | 116 ++++
drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++-
8 files changed, 1561 insertions(+), 20 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
create mode 100644 drivers/bluetooth/btbcm_uart.c
create mode 100644 drivers/bluetooth/btbcm_uart.h

--
1.9.1



2015-05-22 19:05:25

by Ilya Faenson

[permalink] [raw]
Subject: RE: [RFC v3 2/4] Broadcom Bluetooth UART Platform Driver

Hi Arend,

-----Original Message-----
From: Arend van Spriel [mailto:[email protected]]
Sent: Tuesday, May 19, 2015 8:42 AM
To: Ilya Faenson
Cc: Marcel Holtmann; [email protected]
Subject: Re: [RFC v3 2/4] Broadcom Bluetooth UART Platform Driver

On 05/13/15 23:50, Ilya Faenson wrote:
> Introduce the device tree enumerated Broadcom Bluetooth UART driver.
>
> Signed-off-by: Ilya Faenson<[email protected]>
> ---
> drivers/bluetooth/Kconfig | 9 +
> drivers/bluetooth/Makefile | 1 +
> drivers/bluetooth/btbcm_uart.c | 705 +++++++++++++++++++++++++++++++++++++++++
> drivers/bluetooth/btbcm_uart.h | 116 +++++++
> 4 files changed, 831 insertions(+)
> create mode 100644 drivers/bluetooth/btbcm_uart.c
> create mode 100644 drivers/bluetooth/btbcm_uart.h
>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index ed5c273..7127ada 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -128,6 +128,7 @@ config BT_HCIUART_BCM
> bool "Broadcom protocol support"
> depends on BT_HCIUART
> select BT_HCIUART_H4
> + select BT_UART_BCM
> select BT_BCM
> help
> The Broadcom protocol support enables Bluetooth HCI over serial
> @@ -135,6 +136,14 @@ config BT_HCIUART_BCM
>
> Say Y here to compile support for Broadcom protocol.
>
> +config BT_UART_BCM
> + tristate "Broadcom BT UART driver"
> + depends on BT_HCIUART_H4&& TTY
> + help
> + This driver supports the HCI_UART_BT protocol.
> +
> + It manages Bluetooth UART device properties and GPIOs.
> +
> config BT_HCIBCM203X
> tristate "HCI BCM203x USB driver"
> depends on USB
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index dd0d9c4..0e5fd66 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_BT_MRVL) += btmrvl.o
> obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
> obj-$(CONFIG_BT_WILINK) += btwilink.o
> obj-$(CONFIG_BT_BCM) += btbcm.o
> +obj-$(CONFIG_BT_UART_BCM) += btbcm_uart.o
>
> btmrvl-y := btmrvl_main.o
> btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
> diff --git a/drivers/bluetooth/btbcm_uart.c b/drivers/bluetooth/btbcm_uart.c
> new file mode 100644
> index 0000000..2afaed1
> --- /dev/null
> +++ b/drivers/bluetooth/btbcm_uart.c
> @@ -0,0 +1,705 @@
> +/*
> + * Bluetooth BCM UART Driver
> + *
> + * Copyright (c) 2015 Broadcom Corporation
> + *
> + * This file is provided under a dual BSD/GPLv2 license. When using or
> + * redistributing this file, you may do so under either license.
> + *
> + * GPL LICENSE SUMMARY
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + * 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.
> + *
> + * BSD LICENSE SUMMARY
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in
> + * the documentation and/or other materials provided with the
> + * distribution.
> + * Neither the name of Intel Corporation nor the names of its
> + * contributors may be used to endorse or promote products derived
> + * from this software without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include<linux/module.h>
> +
> +#include<linux/kernel.h>
> +#include<linux/init.h>
> +#include<linux/types.h>
> +#include<linux/fcntl.h>
> +#include<linux/interrupt.h>
> +#include<linux/ptrace.h>
> +#include<linux/poll.h>
> +
> +#include<linux/slab.h>
> +#include<linux/tty.h>
> +#include<linux/errno.h>
> +#include<linux/string.h>
> +#include<linux/signal.h>
> +#include<linux/ioctl.h>
> +#include<linux/skbuff.h>
> +#include<linux/list.h>
> +
> +#include<net/bluetooth/bluetooth.h>
> +#include<net/bluetooth/hci_core.h>
> +
> +#include<linux/gpio/consumer.h>
> +#include<linux/of.h>
> +#include<linux/of_gpio.h>
> +#include<linux/of_platform.h>
> +
> +#include "btbcm_uart.h"
> +
> +static int idle_timeout = 5;
> +module_param(idle_timeout, int, 0);
> +MODULE_PARM_DESC(idle_timeout, "Bluetooth idle timeout in seconds");
> +
> +/* Device context */
> +struct bcm_device {
> + struct list_head list;
> +
> + struct platform_device *pdev;
> + struct gpio_desc *bt_wake_gpio;
> + struct gpio_desc *dev_wake_gpio;
> + struct gpio_desc *reg_on_gpio;
> + int bt_wake_irq;
> + int dev_wake_active_low;
> + int reg_on_active_low;
> + int bt_wake_active_low;
> + u32 configure_sleep;
> + u32 manual_fc;
> + u32 baud_rate_before_config_download;
> + u32 configure_audio;
> + u32 PCMClockMode;
> + u32 PCMFillMethod;
> + u32 PCMFillNum;
> + u32 PCMFillValue;
> + u32 PCMInCallBitclock;
> + u32 PCMLSBFirst;
> + u32 PCMRightJustify;
> + u32 PCMRouting;
> + u32 PCMShortFrameSync;
> + u32 PCMSyncMode;
> +
> + char tty_name[64];
> +
> + struct btbcm_uart_callbacks protocol_callbacks;
> + struct work_struct wakeup_work;
> +};
> +
> +/* List of BCM BT UART devices */
> +static DEFINE_SPINLOCK(device_list_lock);
> +static LIST_HEAD(device_list);
> +
> +/*
> + * Calling the BCM protocol at lower execution priority
> + */
> +static void bcm_bt_wakeup_task(struct work_struct *ws)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device =
> + container_of(ws, struct bcm_device, wakeup_work);
> +
> + if (!p_bcm_device) {
> + BT_DBG("bcm_bt_wakeup_task - failing, no device");

Could consider using "%s - ...", __func__ in BT_DBG() macros.

IF: Will change.

> + return;
> + }
> +
> + /* Make sure the device is resumed */
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
> + BT_DBG("bcm_bt_wakeup_task - resume %d written, delaying 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> +
> + /* Let the protocol know it's time to wake up */
> + if (p_bcm_device->protocol_callbacks.p_wakeup)
> + p_bcm_device->protocol_callbacks.p_wakeup(
> + p_bcm_device->protocol_callbacks.context);
> +}
> +
> +/*
> + * Interrupt routine for the wake from the device
> + */
> +static irqreturn_t bcm_bt_uart_isr(int irq, void *context)
> +{
> + unsigned int bt_wake;
> + struct bcm_device *p = (struct bcm_device *)context;
> +
> + bt_wake = gpiod_get_value(p->bt_wake_gpio);
> + BT_DBG("bcm_bt_uart_isr with bt_wake of %d (active_low %d), req bh",
> + bt_wake, p->bt_wake_active_low);
> +
> + /* Defer the actual processing to the platform work queue */
> + schedule_work(&p->wakeup_work);
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * Device instance startup
> + */
> +static int bcm_bt_uart_probe(struct platform_device *pdev)
> +{
> + int ret = 0;
> + struct device_node *np = pdev->dev.of_node;
> + const char *tty_name;
> + struct bcm_device *p_bcm_device = NULL;
> +
> + p_bcm_device = devm_kzalloc(&pdev->dev, sizeof(*p_bcm_device),
> + GFP_KERNEL);
> + if (!p_bcm_device) {
> + BT_DBG("bcm_bt_uart_probe - failing due to no memory");
> + return -ENOMEM;
> + }
> + p_bcm_device->pdev = pdev;
> + BT_DBG("bcm_bt_uart_probe %p context", p_bcm_device);
> +
> + /* Get dev wake GPIO */
> + p_bcm_device->dev_wake_gpio = gpiod_get(&pdev->dev, "bt-wake");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-wake returned %p",
> + p_bcm_device->dev_wake_gpio);
> + if (IS_ERR(p_bcm_device->dev_wake_gpio)) {
> + ret = PTR_ERR(p_bcm_device->dev_wake_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - dev_wake GPIO: %d\n", ret);
> + }
> + p_bcm_device->dev_wake_gpio = NULL;
> + } else {
> + int resume_flag;
> +
> + p_bcm_device->dev_wake_active_low = gpiod_is_active_low
> + (p_bcm_device->dev_wake_gpio);
> + BT_DBG("bcm_bt_uart_probe - dev_wake a-low is %d (cans %d)",
> + p_bcm_device->dev_wake_active_low,
> + gpiod_cansleep(p_bcm_device->dev_wake_gpio));
> +
> + /* configure dev_wake as output with init resumed state */
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + ret = gpiod_direction_output(p_bcm_device->dev_wake_gpio,
> + resume_flag);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s dev_wake GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + goto end;
> + } else {
> + BT_DBG("bcm_bt_uart_probe - dev_wake set to %d",
> + resume_flag);
> + }
> + }
> +
> + /* Get power on/off GPIO */
> + p_bcm_device->reg_on_gpio = gpiod_get(&pdev->dev, "reg-on");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for reg-on returned %p",
> + p_bcm_device->reg_on_gpio);
> + if (IS_ERR(p_bcm_device->reg_on_gpio)) {
> + ret = PTR_ERR(p_bcm_device->reg_on_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - reg_on GPIO: %d\n", ret);
> + }
> + p_bcm_device->reg_on_gpio = NULL;
> + } else {
> + int poweron_flag;
> +
> + p_bcm_device->reg_on_active_low = gpiod_is_active_low
> + (p_bcm_device->reg_on_gpio);
> + BT_DBG("bcm_bt_uart_probe - reg_on a-low is %d (cans %d)",
> + p_bcm_device->reg_on_active_low,
> + gpiod_cansleep(p_bcm_device->reg_on_gpio));
> +
> + /* configure reg_on as output with init on state */
> + poweron_flag = !p_bcm_device->reg_on_active_low;
> + ret = gpiod_direction_output(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s reg_on GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + } else {
> + BT_DBG("bcm_bt_uart_probe - reg_on initially set to %d",
> + poweron_flag);
> + }
> + }
> +
> + platform_set_drvdata(pdev, p_bcm_device);
> + /* Must be done before interrupt is requested */
> + INIT_WORK(&p_bcm_device->wakeup_work, bcm_bt_wakeup_task);
> +
> + /* Get bt host wake GPIO */
> + p_bcm_device->bt_wake_gpio = gpiod_get(&pdev->dev, "bt-host-wake");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-host-wake returned %p",
> + p_bcm_device->bt_wake_gpio);
> + if (IS_ERR(p_bcm_device->bt_wake_gpio)) {
> + ret = PTR_ERR(p_bcm_device->bt_wake_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - bt_wake GPIO: %d\n", ret);
> + }
> + p_bcm_device->bt_wake_gpio = NULL;
> + } else {
> + /* configure bt_wake as input */
> + ret = gpiod_direction_input(p_bcm_device->bt_wake_gpio);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s bt_wake GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + } else {
> + p_bcm_device->bt_wake_active_low = gpiod_is_active_low
> + (p_bcm_device->bt_wake_gpio);
> + BT_DBG("bcm_bt_uart_probe -bt_wake a-low is %d(cans%d)",
> + p_bcm_device->bt_wake_active_low,
> + gpiod_cansleep(p_bcm_device->bt_wake_gpio));
> + p_bcm_device->bt_wake_irq = gpiod_to_irq
> + (p_bcm_device->bt_wake_gpio);
> + if (p_bcm_device->bt_wake_irq< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - HOST_WAKE IRQ: %d\n", ret);
> + } else {
> + unsigned long intflags = IRQF_TRIGGER_RISING;
> +
> + if (p_bcm_device->bt_wake_active_low)
> + intflags = IRQF_TRIGGER_FALLING;
> +
> + ret = request_irq(p_bcm_device->bt_wake_irq,
> + bcm_bt_uart_isr,
> + intflags, "bt_host_wake",
> + p_bcm_device);
> + if (ret< 0) {
> + dev_err(&pdev->dev, "bcm_bt_uart_probe - failed to conf IRQ %d: %d",
> + p_bcm_device->bt_wake_irq, ret);
> + } else {
> + BT_DBG("bcm_bt_uart_probe - IRQ %d",
> + p_bcm_device->bt_wake_irq);
> + }
> + }
> + }
> + }
> +
> + p_bcm_device->configure_sleep = 0;
> + if (!of_property_read_u32(np, "configure-sleep",
> + &p_bcm_device->configure_sleep)) {
> + BT_DBG("configure-sleep read as %d",
> + p_bcm_device->configure_sleep);
> + }
> + p_bcm_device->manual_fc = 0;
> + if (!of_property_read_u32(np, "manual-fc",
> + &p_bcm_device->manual_fc)) {
> + BT_DBG("manual-fc read as %d",
> + p_bcm_device->manual_fc);
> + }
> + p_bcm_device->baud_rate_before_config_download = 3000000;
> + if (!of_property_read_u32(
> + np, "baud-rate-before-config-download",
> + &p_bcm_device->baud_rate_before_config_download)) {
> + BT_DBG("baud-rate-before-config-download read as %d",
> + p_bcm_device->baud_rate_before_config_download);
> + }
> + p_bcm_device->configure_audio = 0;
> + if (!of_property_read_u32(np, "configure-audio",
> + &p_bcm_device->configure_audio)) {
> + BT_DBG("configure-audio read as %d",
> + p_bcm_device->configure_audio);
> + }
> + if (p_bcm_device->configure_audio) {
> + /* Defaults for audio */
> + p_bcm_device->PCMClockMode = 0;
> + p_bcm_device->PCMFillMethod = 2;
> + p_bcm_device->PCMFillNum = 0;
> + p_bcm_device->PCMFillValue = 3;
> + p_bcm_device->PCMInCallBitclock = 0;
> + p_bcm_device->PCMLSBFirst = 0;
> + p_bcm_device->PCMRightJustify = 0;
> + p_bcm_device->PCMRouting = 0;
> + p_bcm_device->PCMShortFrameSync = 0;
> + p_bcm_device->PCMSyncMode = 0;
> +
> + if (!of_property_read_u32(np, "PCMClockMode",
> + &p_bcm_device->PCMClockMode))
> + BT_DBG("PCMClockMode read as %d",
> + p_bcm_device->PCMClockMode);
> + if (!of_property_read_u32(np, "PCMFillMethod",
> + &p_bcm_device->PCMFillMethod))
> + BT_DBG("PCMFillMethod readas %d",
> + p_bcm_device->PCMFillMethod);
> + if (!of_property_read_u32(np, "PCMFillNum",
> + &p_bcm_device->PCMFillNum))
> + BT_DBG("PCMFillNum read as %d",
> + p_bcm_device->PCMFillNum);
> + if (!of_property_read_u32(np, "PCMFillValue",
> + &p_bcm_device->PCMFillValue))
> + BT_DBG("PCMFillValue read as %d",
> + p_bcm_device->PCMFillValue);
> + if (!of_property_read_u32(np, "PCMInCallBitclock",
> + &p_bcm_device->PCMInCallBitclock))
> + BT_DBG("PCMInCallBitclock read as %d",
> + p_bcm_device->PCMInCallBitclock);
> + if (!of_property_read_u32(np, "PCMLSBFirst",
> + &p_bcm_device->PCMLSBFirst))
> + BT_DBG("PCMLSBFirst read as %d",
> + p_bcm_device->PCMLSBFirst);
> + if (!of_property_read_u32(np, "PCMRightJustify",
> + &p_bcm_device->PCMRightJustify))
> + BT_DBG("PCMRightJustify read as %d",
> + p_bcm_device->PCMRightJustify);
> + if (!of_property_read_u32(np, "PCMRouting",
> + &p_bcm_device->PCMRouting))
> + BT_DBG("PCMRouting read as %d",
> + p_bcm_device->PCMRouting);
> + if (!of_property_read_u32(np, "PCMShortFrameSync",
> + &p_bcm_device->PCMShortFrameSync))
> + BT_DBG("PCMShortFrameSync read as %d",
> + p_bcm_device->PCMShortFrameSync);
> + if (!of_property_read_u32(np, "PCMSyncMode",
> + &p_bcm_device->PCMSyncMode))
> + BT_DBG("PCMSyncMode read as %d",
> + p_bcm_device->PCMSyncMode);
> + }
> +
> + if (!of_property_read_string(np, "tty",&tty_name)) {
> + strcpy(p_bcm_device->tty_name, tty_name);
> + BT_DBG("tty name read as %s", p_bcm_device->tty_name);
> + }
> +
> + BT_DBG("idle_timeout set as %d", idle_timeout);
> +
> + ret = 0; /* If we made it here, we're fine */
> +
> + /* Place this instance on the device list */
> + spin_lock(&device_list_lock);
> + list_add_tail(&p_bcm_device->list,&device_list);
> + spin_unlock(&device_list_lock);
> +
> +end:
> + if (ret) {
> + if (p_bcm_device->reg_on_gpio) {
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + }
> + if (p_bcm_device->bt_wake_gpio) {
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + }
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + }
> + }
> +
> + BT_DBG("bcm_bt_uart_probe with the result %d", ret);
> + return ret;
> +}
> +
> +/*
> + * Device instance removal
> + */
> +static int bcm_bt_uart_remove(struct platform_device *pdev)
> +{
> + struct bcm_device *p_bcm_device = platform_get_drvdata(pdev);
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_remove - logic error, no probe?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_remove %p context", p_bcm_device);
> +
> + spin_lock(&device_list_lock);
> + list_del(&p_bcm_device->list);
> + spin_unlock(&device_list_lock);
> +
> + BT_DBG("bcm_bt_uart_remove - freeing interrupt %d",
> + p_bcm_device->bt_wake_irq);
> + free_irq(p_bcm_device->bt_wake_irq, p_bcm_device);
> +
> + if (p_bcm_device->reg_on_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing reg_on_gpio");
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + }
> +
> + if (p_bcm_device->dev_wake_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing dev_wake_gpio");
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + }
> +
> + if (p_bcm_device->bt_wake_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing bt_wake_gpio");
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + }
> +
> + BT_DBG("bcm_bt_uart_remove %p done", p_bcm_device);
> + return 0;
> +}
> +
> +/*
> + * Platform resume callback
> + */
> +static int bcm_bt_uart_resume(struct device *pdev)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device = platform_get_drvdata(
> + to_platform_device(pdev));
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_resume - logic error, no device?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_resume %p", p_bcm_device);
> +
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
> + BT_DBG("bcm_bt_uart_resume: %d written, delaying 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> +
> + /* Let the protocol know the platform is resuming */
> + if (p_bcm_device->protocol_callbacks.p_resume)
> + p_bcm_device->protocol_callbacks.p_resume(
> + p_bcm_device->protocol_callbacks.context);
> +
> + return 0;
> +}
> +
> +/*
> + * Platform suspend callback
> + */
> +static int bcm_bt_uart_suspend(struct device *pdev)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device = platform_get_drvdata(
> + to_platform_device(pdev));
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_suspend - logic error, no device?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_suspend %p", p_bcm_device);
> +
> + /* Let the protocol know the platform is suspending */
> + if (p_bcm_device->protocol_callbacks.p_suspend)
> + p_bcm_device->protocol_callbacks.p_suspend(
> + p_bcm_device->protocol_callbacks.context);
> +
> + /* Suspend the device */
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, !resume_flag);

At first I though there was a mistake being made here with resume_flag
being set on wake gpio as it looked exactly the same as in resume()
callback. I overlooked the second inversion in gpiod_set_value(). I
think it is more clear to rename the variable to 'gpio_value' and say:

gpio_value = !!p_bcm_device->dev_wake_active_low;
gpiod_set_value(p_bcm_device->dev_wake_gpio, gpio_value);

IF: Will rename.

> + BT_DBG("bcm_bt_uart_suspend: %d written, delaying 15 ms",
> + !resume_flag);
> + mdelay(15);
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Entry point for calls from the protocol
> + */
> +int btbcm_uart_control(int action, void *device_context,
> + void *p_data, unsigned long *p_size)

By using multiplexing function and consequently using void pointer
parameters you loose type checking. So are the benefits justifying this
construct.

IF: As I've explained replying to similar question from Marcel,
this is modeled on the ioctl implementation. We may still add
an ioctl here exposing this functionality to the user mode too.

> +{
> + struct btbcm_uart_callbacks *pc;
> + struct btbcm_uart_parameters *pp = p_data; /* for pars action only */
> + int ret = 0;
> + int resume_flag, poweron_flag;
> + struct bcm_device *p_bcm_device = device_context;
> + struct list_head *ptr;
> + bool is_found = false;
> +
> + /* Special processing for the callback configuration */
> + if (action == BTBCM_UART_ACTION_CONFIGURE_CALLBACKS) {
> + pc = p_data;
> +
> + BT_DBG("btbcm_uart_control - configure callbacks");
> + if (p_data == NULL || *p_size != sizeof(struct
> + btbcm_uart_callbacks) || (pc->interface_version !=
> + BTBCM_UART_INTERFACE_VERSION)) {
> + BT_DBG("btbcm_uart_control - callbacks mismatch!");
> + return -E2BIG;
> + }
> +
> + BT_DBG("btbcm_uart_control - configure callbacks for %s(%p)",
> + pc->name, pc->context);
> + if (p_bcm_device == NULL) {
> + spin_lock(&device_list_lock);
> + list_for_each(ptr,&device_list) {
> + p_bcm_device = list_entry(ptr, struct
> + bcm_device, list);
> + if (!strcmp(p_bcm_device->tty_name, pc->name)) {
> + is_found = true;
> + break;
> + }
> + }
> +
> + spin_unlock(&device_list_lock);
> + if (!is_found) {
> + BT_DBG("btbcm_uart_control - no device!");
> + return -ENOENT;
> + }
> + }
> +
> + p_bcm_device->protocol_callbacks = *pc;
> + memcpy(p_data,&p_bcm_device, sizeof(p_bcm_device));
> + *p_size = sizeof(p_bcm_device);
> + return ret;
> + }
> +
> + /* All other requests must have the right context */
> + if (p_bcm_device == NULL) {
> + BT_DBG("btbcm_uart_control - failing, no device");
> + return -ENOENT;
> + }
> +
> + switch (action) {
> + case BTBCM_UART_ACTION_POWER_ON:
> + BT_DBG("btbcm_uart_control %p - power on", device_context);
> + if (p_bcm_device->reg_on_gpio) {
> + poweron_flag = !p_bcm_device->reg_on_active_low;
> + gpiod_set_value(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + BT_DBG("btbcm_uart_control - pwron %d, delay 15 ms",
> + poweron_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_POWER_OFF:
> + BT_DBG("btbcm_uart_control %p - power off", device_context);
> + if (p_bcm_device->reg_on_gpio) {
> + poweron_flag = p_bcm_device->reg_on_active_low;
> + gpiod_set_value(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + BT_DBG("btbcm_uart_control - pwroff %d, delay 15 ms",
> + poweron_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_RESUME:
> + BT_DBG("btbcm_uart_control %p - resume", device_context);
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio,
> + resume_flag);
> + BT_DBG("btbcm_uart_control - resume %d, delay 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_SUSPEND:
> + BT_DBG("btbcm_uart_control %p - suspend", device_context);
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio,
> + !resume_flag);
> + BT_DBG("btbcm_uart_control - suspend %d, delay 15ms",
> + !resume_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_GET_PARAMETERS:
> + BT_DBG("btbcm_uart_control %p - get pars", device_context);
> + if ((p_data == NULL) ||
> + (*p_size< sizeof(struct btbcm_uart_parameters))) {
> + BT_DBG("btbcm_uart_control - failing, wrong par size");
> + return -E2BIG;
> + }
> +
> + memset(pp, 0, sizeof(struct btbcm_uart_parameters));
> + pp->interface_version = BTBCM_UART_INTERFACE_VERSION;
> + pp->configure_sleep = p_bcm_device->configure_sleep;
> + pp->manual_fc = p_bcm_device->manual_fc;
> + pp->dev_wake_active_low = p_bcm_device->dev_wake_active_low;
> + pp->bt_wake_active_low = p_bcm_device->bt_wake_active_low;
> + pp->idle_timeout_in_secs = idle_timeout;
> + pp->baud_rate_before_config_download =
> + p_bcm_device->baud_rate_before_config_download;
> + pp->configure_audio = p_bcm_device->configure_audio;
> + pp->PCMClockMode = p_bcm_device->PCMClockMode;
> + pp->PCMFillMethod = p_bcm_device->PCMFillMethod;
> + pp->PCMFillNum = p_bcm_device->PCMFillNum;
> + pp->PCMFillValue = p_bcm_device->PCMFillValue;
> + pp->PCMInCallBitclock = p_bcm_device->PCMInCallBitclock;
> + pp->PCMLSBFirst = p_bcm_device->PCMLSBFirst;
> + pp->PCMRightJustify = p_bcm_device->PCMRightJustify;
> + pp->PCMRouting = p_bcm_device->PCMRouting;
> + pp->PCMShortFrameSync = p_bcm_device->PCMShortFrameSync;
> + pp->PCMSyncMode = p_bcm_device->PCMSyncMode;
> + *p_size = sizeof(struct btbcm_uart_parameters);
> + break;
> +
> + default:
> + BT_DBG("btbcm_uart_control %p unknown act %d",
> + device_context, action);
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(btbcm_uart_control);
> +
> +/* Platform susp and resume callbacks */
> +static SIMPLE_DEV_PM_OPS(bcm_bt_uart_pm_ops,
> + bcm_bt_uart_suspend, bcm_bt_uart_resume);
> +
> +/* Driver match table */
> +static const struct of_device_id bcm_bt_uart_table[] = {
> + { .compatible = "brcm,brcm-bt-uart" },
> + {}
> +};
> +
> +/* Driver configuration */
> +static struct platform_driver bcm_bt_uart_driver = {
> + .probe = bcm_bt_uart_probe,
> + .remove = bcm_bt_uart_remove,
> + .driver = {
> + .name = "brcm_bt_uart",
> + .of_match_table = of_match_ptr(bcm_bt_uart_table),
> + .owner = THIS_MODULE,
> + .pm =&bcm_bt_uart_pm_ops,
> + },
> +};
> +
> +module_platform_driver(bcm_bt_uart_driver);
> +
> +MODULE_AUTHOR("Ilya Faenson");
> +MODULE_DESCRIPTION("Broadcom Bluetooth UART Driver");
> +MODULE_LICENSE("Dual BSD/GPL");
> +
> diff --git a/drivers/bluetooth/btbcm_uart.h b/drivers/bluetooth/btbcm_uart.h
> new file mode 100644
> index 0000000..3b02a4e
> --- /dev/null
> +++ b/drivers/bluetooth/btbcm_uart.h
> @@ -0,0 +1,116 @@
> +/*
> + * Bluetooth BCM UART Driver Header
> + *
> + * Copyright (c) 2015 Broadcom Corporation
> + *
> + * This file is provided under a dual BSD/GPLv2 license. When using or
> + * redistributing this file, you may do so under either license.
> + *
> + * GPL LICENSE SUMMARY
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + * 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.
> + *
> + * BSD LICENSE SUMMARY
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in
> + * the documentation and/or other materials provided with the
> + * distribution.
> + * Neither the name of Intel Corporation nor the names of its

Guess you copied this somewhere. Should it say Broadcom Corporation
instead? Actually there is no such thing as a dual BSD/GPLv2 license.
The MODULE_LICENSE value "Dual BSD/GPL" only indicates that the license
for this file is compatible with the terms of both BSD and GPLv2. That
said we have internal guideline on license statements in source files. I
will let you know where to find those privately.

IF: Thanks for the clarifications. Will change to GPL as per internal discussion.

Regards,
Arend

> + * contributors may be used to endorse or promote products derived
> + * from this software without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#ifndef BTBCM_UART_H
> +#define BTBCM_UART_H
> +
> +/* Change the version if you change anything in this header */
> +#define BTBCM_UART_INTERFACE_VERSION 1
> +/* Callbacks from the driver into the protocol */
> +typedef void (*p_suspend_callback)(void *context);
> +typedef void (*p_resume_callback)(void *context);
> +typedef void (*p_wakeup_callback)(void *context);
> +struct btbcm_uart_callbacks {
> + int interface_version; /* interface # compiled against */
> + void *context; /* protocol instance context */
> + char name[64]; /* protocol tty device, for example, ttyS0 */
> +
> + /* client callbacks */
> + p_suspend_callback p_suspend;
> + p_resume_callback p_resume;
> + p_wakeup_callback p_wakeup;
> +};
> +
> +/* Driver parameters retrieved from the DT or ACPI */
> +struct btbcm_uart_parameters {
> + int interface_version; /* interface # compiled against */
> +
> + /* Parameters themselves */
> + int configure_sleep;
> + int manual_fc;
> + int dev_wake_active_low;
> + int bt_wake_active_low;
> + int idle_timeout_in_secs;
> + int baud_rate_before_config_download;
> + int configure_audio;
> + int PCMClockMode;
> + int PCMFillMethod;
> + int PCMFillNum;
> + int PCMFillValue;
> + int PCMInCallBitclock;
> + int PCMLSBFirst;
> + int PCMRightJustify;
> + int PCMRouting;
> + int PCMShortFrameSync;
> + int PCMSyncMode;
> +};
> +
> +/*
> + * Actions on the BTBCM_UART driver
> + */
> +
> +/* Configure protocol callbacks */
> +#define BTBCM_UART_ACTION_CONFIGURE_CALLBACKS 0
> +
> +/* Retrieve BT device parameters */
> +#define BTBCM_UART_ACTION_GET_PARAMETERS 1
> +
> +/* Resume the BT device via GPIO */
> +#define BTBCM_UART_ACTION_RESUME 2
> +
> +/* Suspend the BT device via GPIO */
> +#define BTBCM_UART_ACTION_SUSPEND 3
> +
> +/* Power the BT device off via GPIO */
> +#define BTBCM_UART_ACTION_POWER_OFF 4
> +
> +/* Power the BT device on via GPIO */
> +#define BTBCM_UART_ACTION_POWER_ON 5
> +
> +/* Execute an action on the BT device */
> +extern int btbcm_uart_control(int action, void *device_context,
> + void *p_data, unsigned long *p_size);
> +
> +#endif
> +


2015-05-22 19:05:20

by Ilya Faenson

[permalink] [raw]
Subject: RE: [RFC v3 1/4] Broadcom Bluetooth UART Device Tree bindings

Hi Arend,

-----Original Message-----
From: Arend van Spriel [mailto:[email protected]]=20
Sent: Tuesday, May 19, 2015 5:23 AM
To: Ilya Faenson
Cc: Marcel Holtmann; [email protected]
Subject: Re: [RFC v3 1/4] Broadcom Bluetooth UART Device Tree bindings

On 05/13/15 23:50, Ilya Faenson wrote:
> Device Tree bindings to configure the Broadcom Bluetooth UART device.

As Marcel indicated this patch needs to be reviewed by devicetree folks=20
as well so add following to the commit message:

IF: Will do, thanks.

Cc: [email protected]
Cc: [email protected]
> Signed-off-by: Ilya Faenson<[email protected]>
> ---
> .../devicetree/bindings/net/bluetooth/btbcm.txt | 54 +++++++++++++++=
+++++++
> 1 file changed, 54 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/bluetooth/btbc=
m.txt
>
> diff --git a/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt b/=
Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
> new file mode 100644
> index 0000000..cc9f225
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
> @@ -0,0 +1,54 @@
> +btbcm
> +------
> +
> +Required properties:
> +
> + - compatible : must be "brcm,brcm-bt-uart".

The devicetree folks will probably want to see a specific device id=20
here. Not sure where there are means to obtain that from the device=20
itself, which could be an argument not to use it.

IF: Scores of BT UART chips will be supported so we don't all of them
to be listed. Specific chip will be listed in the device node name - see
the example.

> + - tty : tty device connected to this Bluetooth device.
> +
> +Optional properties:
> +
> + - bt-host-wake-gpios : bt-host-wake input GPIO to be used as an interr=
upt.
> +
> + - bt-wake-gpios : bt-wake output GPIO to be used to suspend / resume d=
evice.
> +
> + - reg-on-gpios : reg-on output GPIO to be used to power device on/off.

Maybe use bt-reg-on-gpios for sake of consistency with other gpio=20
properties.

IF: Will change.

> + - baud-rate-before-config-download : initial Bluetooth device baud rat=
e.
> + Default: 3000000.
> +
> + - manual-fc : flow control UART in suspend / resume scenarios.
> + Default: 0.
> +
> + - configure-sleep : configure suspend / resume flag.
> + Default: 0.
> +
> + - configure-audio : configure platform PCM SCO flag.
> + Default: 0.
> +
> + - PCM* : SCO PCM platform parameters. Work with Broadcom on setting.

What does "Work with Broadcom on setting" mean here?

IF: Will add individual PCM property descriptions.

> + Defaults: see the example below.

Devicetree properties that are for a specific device should be have=20
vendor prefix. I can imagine some of the properties are generic to any=20
bt device and/or bt-behind-uart.

IF: All these properties apply to all Broadcom BT UART devices.

> +Example:
> +
> + bcm4354_bt_uart: bcm4354-bt-uart {
> + compatible =3D "bcm-bt-uart,bcm4354-bt-uart";

This compatible string here should match the described compatible=20
property above.

IF: Will change (it's a doc issue only).

> + bt-wake-gpios =3D<&gpio4 30 GPIO_ACTIVE_HIGH>;
> + bt-host-wake-gpios =3D<&gpio4 31 GPIO_ACTIVE_HIGH>;
> + tty =3D "ttyS0";
> + baud-rate-before-config-download =3D<3000000>;
> + configure-sleep =3D<1>;
> + configure-audio =3D<1>;

Are these configure flags boolean. If so you can drop the assignment.=20
The presence of the property itself can be used.

IF: Will change.

Regards,
Arend

> + PCMClockMode =3D<0>;
> + PCMFillMethod =3D<2>;
> + PCMFillNum =3D<0>;
> + PCMFillValue =3D<3>;
> + PCMInCallBitclock =3D<0>;
> + PCMLSBFirst =3D<0>;
> + PCMRightJustify =3D<0>;
> + PCMRouting =3D<0>;
> + PCMShortFrameSync =3D<0>;
> + PCMSyncMode =3D<0>;
> + };
> +

2015-05-20 07:19:19

by Arend van Spriel

[permalink] [raw]
Subject: Re: [RFC v3 4/4] BlueZ Broadcom UART Protocol

On 05/19/15 17:20, Marcel Holtmann wrote:
> Hi Arend,
>
>>> Enhance Broadcom protocol with the UART setup, firmware download
>>> and power management.
>>>
>>> Signed-off-by: Ilya Faenson<[email protected]>
>>> ---
>>> drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++++++++++++++++++++++++++++++--
>>> 1 file changed, 513 insertions(+), 15 deletions(-)
>>>
>>> diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
>>> index 1ec0b4a..ccd92ed 100644
>>> --- a/drivers/bluetooth/hci_bcm.c
>>> +++ b/drivers/bluetooth/hci_bcm.c
>>> @@ -1,8 +1,9 @@
>>> /*
>>> *
>>> - * Bluetooth HCI UART driver for Broadcom devices
>>> + * Bluetooth UART H4 protocol for Broadcom devices
>>> *
>>> * Copyright (C) 2015 Intel Corporation
>>> + * Copyright (C) 2015 Broadcom Corporation
>>> *
>>> *
>>> * This program is free software; you can redistribute it and/or modify
>>> @@ -21,48 +22,413 @@
>>> *
>>> */
>>>
>>> +#include<linux/module.h>
>>> #include<linux/kernel.h>
>>> +#include<linux/init.h>
>>> +#include<linux/types.h>
>>> +#include<linux/fcntl.h>
>>> +#include<linux/interrupt.h>
>>> +#include<linux/ptrace.h>
>>> +#include<linux/poll.h>
>>> +#include<linux/slab.h>
>>> +#include<linux/tty.h>
>>> #include<linux/errno.h>
>>> +#include<linux/string.h>
>>> +#include<linux/signal.h>
>>> +#include<linux/ioctl.h>
>>> #include<linux/skbuff.h>
>>> +#include<linux/gpio/consumer.h>
>>> +#include<linux/of_gpio.h>
>>> +#include<linux/of_platform.h>
>>>
>>> #include<net/bluetooth/bluetooth.h>
>>> #include<net/bluetooth/hci_core.h>
>>>
>>> -#include "btbcm.h"
>>> #include "hci_uart.h"
>>> +#include "btbcm.h"
>>> +#include "btbcm_uart.h"
>>>
>>> struct bcm_data {
>>> struct sk_buff *rx_skb;
>>> struct sk_buff_head txq;
>>> + struct hci_uart *hu;
>>> +
>>> + bool is_suspended; /* suspend/resume flag */
>>> +
>>> + struct timer_list timer; /* idle timer */
>>> +
>>> + struct btbcm_uart_parameters pars; /* device parameters */
>>> + void *device_context; /* ACPI/DT device context */
>>> };
>>>
>>> +/* Suspend/resume synchronization mutex */
>>> +static DEFINE_MUTEX(plock);
>>> +
>>> +/*
>>> + * Callbacks from the BCMBT_UART device
>>> + */
>>> +
>>> +/*
>>> + * The platform is suspending. Stop UART activity
>>> + */
>>> +static void suspend_notification(void *context)
>>> +{
>>> + struct ktermios ktermios;
>>> + struct hci_uart *hu = (struct hci_uart *)context;
>>> + struct bcm_data *h4 = hu->priv;
>>> + struct tty_struct *tty = hu->tty;
>>> + int status;
>>> + unsigned int set = 0;
>>> + unsigned int clear = 0;
>>> +
>>> + BT_DBG("suspend_notification with is_suspended %d", h4->is_suspended);
>>> +
>>> + if (!h4->pars.configure_sleep)
>>> + return;
>>> +
>>> + if (!h4->is_suspended) {
>>> + if (h4->pars.manual_fc) {
>>> + /* Disable hardware flow control */
>>> + ktermios = tty->termios;
>>> + ktermios.c_cflag&= ~CRTSCTS;
>>> + status = tty_set_termios(tty,&ktermios);
>>> + if (status)
>>> + BT_DBG("suspend_notification dis fc fail %d",
>>> + status);
>>> + else
>>> + BT_DBG("suspend_notification hw fc disabled");
>>> +
>>> + /* Clear RTS to prevent the device from sending */
>>> + /* (most PCs need OUT2 to enable interrupts) */
>>> + status = tty->driver->ops->tiocmget(tty);
>>> + BT_DBG("suspend_notification cur tiocm 0x%x", status);
>>> + set&= ~(TIOCM_OUT2 | TIOCM_RTS);
>>> + clear = ~set;
>>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>>> + if (status)
>>> + BT_DBG("suspend_notification clr RTS fail %d",
>>> + status);
>>> + else
>>> + BT_DBG("suspend_notification RTS cleared");
>>> + status = tty->driver->ops->tiocmget(tty);
>>> + BT_DBG("suspend_notification end tiocm 0x%x", status);
>>> + }
>>> +
>>> + /* Once this callback returns, driver suspends BT via GPIO */
>>> + h4->is_suspended = true;
>>> + }
>>> +}
>>> +
>>> +/*
>>> + * The platform is resuming. Resume UART activity.
>>> + */
>>> +static void resume_notification(void *context)
>>> +{
>>> + struct ktermios ktermios;
>>> + struct hci_uart *hu = (struct hci_uart *)context;
>>> + struct bcm_data *h4 = hu->priv;
>>> + struct tty_struct *tty = hu->tty;
>>> + int status;
>>> + unsigned int set = 0;
>>> + unsigned int clear = 0;
>>> +
>>> + BT_DBG("resume_notification with is_suspended %d", h4->is_suspended);
>>> +
>>> + if (!h4->pars.configure_sleep)
>>> + return;
>>> +
>>> + /* When this callback executes, the device has woken up already */
>>> + if (h4->is_suspended) {
>>> + h4->is_suspended = false;
>>> +
>>> + if (h4->pars.manual_fc) {
>>> + status = tty->driver->ops->tiocmget(tty);
>>> + BT_DBG("resume_notification cur tiocm 0x%x", status);
>>> + set |= (TIOCM_OUT2 | TIOCM_RTS);
>>> + clear = ~set;
>>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>>> + if (status)
>>> + BT_DBG("resume_notification set RTS fail %d",
>>> + status);
>>> + else
>>> + BT_DBG("resume_notification RTS set");
>>> +
>>> + /* Re-enable hardware flow control */
>>> + ktermios = tty->termios;
>>> + ktermios.c_cflag |= CRTSCTS;
>>> + status = tty_set_termios(tty,&ktermios);
>>> + if (status)
>>> + BT_DBG("resume_notification enable fc fail %d",
>>> + status);
>>> + else
>>> + BT_DBG("resume_notification hw fc re-enabled");
>>> + }
>>> + }
>>> +
>>> + /* If we're resumed, the idle timer must be running */
>>> + status = mod_timer(&h4->timer, jiffies +
>>> + msecs_to_jiffies(h4->pars.idle_timeout_in_secs * 1000));
>>> +}
>>> +
>>> +/*
>>> + * The BT device is resuming. Resume UART activity if suspended
>>> + */
>>> +static void wakeup_notification(void *context)
>>> +{
>>> + struct ktermios ktermios;
>>> + struct hci_uart *hu = (struct hci_uart *)context;
>>> + struct bcm_data *h4 = hu->priv;
>>> + struct tty_struct *tty = hu->tty;
>>> + int status;
>>> + unsigned int set = 0;
>>> + unsigned int clear = 0;
>>> +
>>> + if (!h4->pars.configure_sleep)
>>> + return;
>>> +
>>> + status = tty->driver->ops->tiocmget(tty);
>>> + BT_DBG("wakeup_notification hu %p current tiocm 0x%x", hu, status);
>>> + if (h4->is_suspended) {
>>> + if (h4->pars.manual_fc) {
>>> + set |= (TIOCM_OUT2 | TIOCM_RTS);
>>> + clear = ~set;
>>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>>> + TIOCM_OUT2 | TIOCM_LOOP;
>>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>>> + if (status)
>>> + BT_DBG("wakeup_notification set RTS fail %d",
>>> + status);
>>> + else
>>> + BT_DBG("wakeup_notification RTS set");
>>> +
>>> + /* Re-enable hardware flow control */
>>> + ktermios = tty->termios;
>>> + ktermios.c_cflag |= CRTSCTS;
>>> + status = tty_set_termios(tty,&ktermios);
>>> + if (status)
>>> + BT_DBG("wakeup_notification fc-en failure %d",
>>> + status);
>>> + else
>>> + BT_DBG("wakeup_notification hw fc re-enabled");
>>> + }
>>> +
>>> + h4->is_suspended = false;
>>> + }
>>> +
>>> + /* If we're resumed, the idle timer must be running */
>>> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>>> + h4->pars.idle_timeout_in_secs * 1000));
>>> +}
>>> +
>>> +/*
>>> + * Make sure we're awake
>>> + * (called when the resumed state is required)
>>> + */
>>> +static void bcm_ensure_wakeup(struct hci_uart *hu)
>>> +{
>>> + struct bcm_data *h4 = hu->priv;
>>> + int status;
>>> +
>>> + if (!h4->pars.configure_sleep)
>>> + return;
>>> +
>>> + /* Suspend/resume operations are serialized */
>>> + mutex_lock(&plock);
>>> +
>>> + /* Nothing to do if resumed already */
>>> + if (!h4->is_suspended) {
>>> + mutex_unlock(&plock);
>>> +
>>> + /* Just reset the timer */
>>> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>>> + h4->pars.idle_timeout_in_secs * 1000));
>>> + return;
>>> + }
>>> +
>>> + /* Wakeup the device */
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_RESUME,
>>> + h4->device_context, NULL, NULL);
>>> + if (status)
>>> + BT_DBG("bcm_ensure_wakeup failed to resume driver %d", status);
>>> +
>>> + /* Unflow control the port if configured */
>>> + resume_notification(hu);
>>> +
>>> + mutex_unlock(&plock);
>>> +}
>>> +
>>> +/*
>>> + * Idle timer callback
>>> + */
>>> +static void bcm_idle_timeout(unsigned long arg)
>>> +{
>>> + struct hci_uart *hu = (struct hci_uart *)arg;
>>> + struct bcm_data *h4 = hu->priv;
>>> + int status;
>>> +
>>> + BT_DBG("bcm_idle_timeout hu %p", hu);
>>> +
>>> + /* Suspend/resume operations are serialized */
>>> + mutex_lock(&plock);
>>> +
>>> + if (!h4->is_suspended) {
>>> + /* Flow control the port if configured */
>>> + suspend_notification(hu);
>>> +
>>> + /* Suspend the device */
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
>>> + h4->device_context, NULL, NULL);
>>> + if (status)
>>> + BT_DBG("bcm_idle_timeout failed to suspend device %d",
>>> + status);
>>> + }
>>> +
>>> + mutex_unlock(&plock);
>>> +}
>>> +
>>> static int bcm_open(struct hci_uart *hu)
>>> {
>>> - struct bcm_data *bcm;
>>> + struct btbcm_uart_callbacks callbacks;
>>> + unsigned long callbacks_size = sizeof(callbacks);
>>> + int status;
>>> + struct bcm_data *h4;
>>> + struct tty_struct *tty = hu->tty;
>>>
>>> - BT_DBG("hu %p", hu);
>>> + BT_DBG("bcm_h4_open hu %p", hu);
>>>
>>> - bcm = kzalloc(sizeof(*bcm), GFP_KERNEL);
>>> - if (!bcm)
>>> + h4 = kzalloc(sizeof(*h4), GFP_KERNEL);
>>> + if (!h4)
>>> return -ENOMEM;
>>>
>>> - skb_queue_head_init(&bcm->txq);
>>> + skb_queue_head_init(&h4->txq);
>>> + hu->priv = h4;
>>> + h4->hu = hu;
>>> + h4->is_suspended = false;
>>> +
>>> + /* Configure callbacks on the driver */
>>> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
>>> + callbacks.context = hu;
>>> + strcpy(callbacks.name, tty->name);
>>> + callbacks.p_suspend = suspend_notification;
>>> + callbacks.p_resume = resume_notification;
>>> + callbacks.p_wakeup = wakeup_notification;
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
>>> + NULL,&callbacks,&callbacks_size);
>>> + if (status) {
>>> + BT_DBG("bcm_h4_open failed to set driver callbacks %d", status);
>>> + return status;
>>> + }
>>> + if (callbacks_size != sizeof(void *)) {
>>> + BT_DBG("bcm_h4_open got back %d bytes from callbacks?!",
>>> + (int)callbacks_size);
>>> + return -EMSGSIZE;
>>> + }
>>> + memcpy(&h4->device_context,&callbacks, sizeof(void *));
>>> + BT_DBG("bcm_h4_open callbacks context %p", h4->device_context);
>>> +
>>> + /* Retrieve device parameters */
>>> + callbacks_size = sizeof(h4->pars);
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_GET_PARAMETERS,
>>> + h4->device_context,&h4->pars,
>>> + &callbacks_size);
>>> + if (status) {
>>> + BT_DBG("bcm_h4_open failed to get dev parameters %d", status);
>>> + return status;
>>> + }
>>> + BT_DBG("Pars ver %d csleep %d dalow %d balow %d idle %d",
>>> + h4->pars.interface_version, h4->pars.configure_sleep,
>>> + h4->pars.dev_wake_active_low, h4->pars.bt_wake_active_low,
>>> + h4->pars.idle_timeout_in_secs);
>>> +
>>> + /* Cycle power to make sure the device is in the known state */
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
>>> + h4->device_context, NULL, NULL);
>>> + if (status) {
>>> + BT_DBG("bcm_h4_open failed to power off %d", status);
>>> + } else {
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_ON,
>>> + h4->device_context, NULL, NULL);
>>> + if (status)
>>> + BT_DBG("bcm_h4_open failed to power on %d", status);
>>> + }
>>> +
>>> + /* Start the idle timer */
>>> + if (h4->pars.configure_sleep) {
>>> + setup_timer(&h4->timer, bcm_idle_timeout, (unsigned long)hu);
>>> + if (h4->pars.configure_sleep)
>>> + mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>>> + h4->pars.idle_timeout_in_secs * 1000));
>>> + }
>>>
>>> - hu->priv = bcm;
>>> return 0;
>>> }
>>>
>>> static int bcm_close(struct hci_uart *hu)
>>> {
>>> - struct bcm_data *bcm = hu->priv;
>>> + struct btbcm_uart_callbacks callbacks;
>>> + unsigned long callbacks_size = sizeof(callbacks);
>>> + struct bcm_data *h4 = hu->priv;
>>> + int status;
>>>
>>> - BT_DBG("hu %p", hu);
>>> + hu->priv = NULL;
>>>
>>> - skb_queue_purge(&bcm->txq);
>>> - kfree_skb(bcm->rx_skb);
>>> - kfree(bcm);
>>> + BT_DBG("bcm_h4_close hu %p", hu);
>>> +
>>> + /* If we're being closed, we must suspend */
>>> + if (h4->pars.configure_sleep) {
>>> + mutex_lock(&plock);
>>> +
>>> + if (!h4->is_suspended) {
>>> + /* Flow control the port */
>>> + suspend_notification(hu);
>>> +
>>> + /* Suspend the device */
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
>>> + h4->device_context, NULL,
>>> + NULL);
>>> + if (status) {
>>> + BT_DBG("bcm_h4_close suspend driver fail %d",
>>> + status);
>>> + }
>>> + }
>>> +
>>> + mutex_unlock(&plock);
>>> +
>>> + del_timer_sync(&h4->timer);
>>> + }
>>> +
>>> + /* Power off the device if possible */
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
>>> + h4->device_context, NULL, NULL);
>>> + if (status)
>>> + BT_DBG("bcm_h4_close failed to power off %d", status);
>>> +
>>> + /* de-configure callbacks on the driver */
>>> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
>>> + callbacks.context = hu;
>>> + callbacks.p_suspend = NULL;
>>> + callbacks.p_resume = NULL;
>>> + callbacks.p_wakeup = NULL;
>>> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
>>> + h4->device_context,&callbacks,
>>> + &callbacks_size);
>>> + if (status)
>>> + BT_DBG("bcm_h4_close failed to reset drv callbacks %d", status);
>>> + skb_queue_purge(&h4->txq);
>>>
>>> hu->priv = NULL;
>>> + kfree(h4);
>>> +
>>> return 0;
>>> }
>>>
>>> @@ -79,11 +445,137 @@ static int bcm_flush(struct hci_uart *hu)
>>>
>>> static int bcm_setup(struct hci_uart *hu)
>>> {
>>> - BT_DBG("hu %p", hu);
>>> + struct bcm_data *h4 = hu->priv;
>>> + int status;
>>> + struct sk_buff *skb;
>>> + unsigned char sleep_pars[] = {
>>> + 0x01, /* sleep mode 1=UART */
>>> + 0x02, /* idle threshold HOST (value * 300ms) */
>>> + 0x02, /* idle threshold HC (value * 300ms) */
>>> + 0x01, /* BT_WAKE active mode - 1=active high, 0 = active low */
>>> + 0x00, /* HOST_WAKE active mode - 1=active high, 0 = active low */
>>> + 0x01, /* Allow host sleep during SCO - FALSE */
>>> + 0x01, /* combine sleep mode and LPM - 1 == TRUE */
>>> + 0x00, /* enable tristate control of UART TX line - FALSE */
>>> + 0x00, /* USB auto-sleep on USB SUSPEND */
>>> + 0x00, /* USB USB RESUME timeout (seconds) */
>>> + 0x00, /* Pulsed Host Wake */
>>> + 0x00 /* Enable Break To Host */
>>> + };
>>> + unsigned char pcm_int_pars[] = {
>>> + 0x00, /* 0=PCM routing, 1=SCO over HCI */
>>> + 0x02, /* 0=128Kbps,1=256Kbps,2=512Kbps,3=1024Kbps,4=2048Kbps */
>>> + 0x00, /* short frame sync 0=short, 1=long */
>>> + 0x00, /* sync mode 0=slave, 1=master */
>>> + 0x00 /* clock mode 0=slave, 1=master */
>>> + };
>>> + unsigned char pcm_format_pars[] = {
>>> + 0x00, /* LSB_First 1=TRUE, 0=FALSE */
>>> + 0x00, /* Fill_Value (use 0-7 for fill bits value) */
>>> + 0x02, /* Fill_Method (2=sign extended) */
>>> + 0x03, /* Fill_Num # of fill bits 0-3) */
>>> + 0x01 /* Right_Justify 1=TRUE, 0=FALSE */
>>> + };
>>> + unsigned char time_slot_number = 0;
>>> +
>>> + BT_DBG("bcm_h4_setup hu %p", hu);
>>> +
>>> + /* Bring the UART into known default state */
>>> + status = btbcm_init_uart(hu);
>>> + if (status) {
>>> + BT_DBG("bcm_h4_setup failed to init BT device %d", status);
>>> + return status;
>>> + }
>>> +
>>> + /* Basic sanity check */
>>> + skb = __hci_cmd_sync(hu->hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT);
>>> + if (IS_ERR(skb)) {
>>> + status = PTR_ERR(skb);
>>> + BT_ERR("bcm_h4_setup HCI Reset failed (%d)", status);
>>> + return status;
>>> + }
>>> + kfree_skb(skb);
>>> + BT_DBG("bcm_h4_setup HCI Reset succeeded");
>>> +
>>> + /* Set the new baud rate */
>>> + status = btbcm_set_baud_rate(hu,
>>> + h4->pars.baud_rate_before_config_download);
>>> + if (status) {
>>> + BT_ERR("bcm_h4_setup set_baud_rate faiilure %d", status);
>>> + return status;
>>> + }
>>>
>>> hu->hdev->set_bdaddr = btbcm_set_bdaddr;
>>>
>>> - return btbcm_setup_patchram(hu->hdev);
>>> + /* Download the firmware and reconfigure the UART afterwards */
>>> + status = btbcm_setup_patchram(hu->hdev);
>>> + if (status) {
>>> + BT_ERR("bcm_h4_setup setup_patchram faiilure %d", status);
>>> + return status;
>>> + }
>>> +
>>> + /* Configure SCO PCM parameters */
>>> + if (h4->pars.configure_audio) {
>>> + pcm_int_pars[0] = h4->pars.PCMRouting;
>>> + pcm_int_pars[1] = h4->pars.PCMInCallBitclock;
>>> + pcm_int_pars[2] = h4->pars.PCMShortFrameSync;
>>> + pcm_int_pars[3] = h4->pars.PCMSyncMode;
>>> + pcm_int_pars[4] = h4->pars.PCMClockMode;
>>> + skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_pars),
>>
>> Guess 0xfc1c is the hci command. Can you create a define for them?
>
> in a lot of cases we kept the hex number and just included a proper comment above the command to explain what this command does. That is at least how we have done it inside the Intel vendor support. I am fine with also creating a define for it. However sometimes they got heavily complicated and long.

Ok. As there is already a comment at the if statement above I suppose it
is clear and my remark can be ignored. Fine by me.

Thanks,
Arend

2015-05-19 15:20:31

by Marcel Holtmann

[permalink] [raw]
Subject: Re: [RFC v3 4/4] BlueZ Broadcom UART Protocol

Hi Arend,

>> Enhance Broadcom protocol with the UART setup, firmware download
>> and power management.
>>
>> Signed-off-by: Ilya Faenson<[email protected]>
>> ---
>> drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++++++++++++++++++++++++++++++--
>> 1 file changed, 513 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
>> index 1ec0b4a..ccd92ed 100644
>> --- a/drivers/bluetooth/hci_bcm.c
>> +++ b/drivers/bluetooth/hci_bcm.c
>> @@ -1,8 +1,9 @@
>> /*
>> *
>> - * Bluetooth HCI UART driver for Broadcom devices
>> + * Bluetooth UART H4 protocol for Broadcom devices
>> *
>> * Copyright (C) 2015 Intel Corporation
>> + * Copyright (C) 2015 Broadcom Corporation
>> *
>> *
>> * This program is free software; you can redistribute it and/or modify
>> @@ -21,48 +22,413 @@
>> *
>> */
>>
>> +#include<linux/module.h>
>> #include<linux/kernel.h>
>> +#include<linux/init.h>
>> +#include<linux/types.h>
>> +#include<linux/fcntl.h>
>> +#include<linux/interrupt.h>
>> +#include<linux/ptrace.h>
>> +#include<linux/poll.h>
>> +#include<linux/slab.h>
>> +#include<linux/tty.h>
>> #include<linux/errno.h>
>> +#include<linux/string.h>
>> +#include<linux/signal.h>
>> +#include<linux/ioctl.h>
>> #include<linux/skbuff.h>
>> +#include<linux/gpio/consumer.h>
>> +#include<linux/of_gpio.h>
>> +#include<linux/of_platform.h>
>>
>> #include<net/bluetooth/bluetooth.h>
>> #include<net/bluetooth/hci_core.h>
>>
>> -#include "btbcm.h"
>> #include "hci_uart.h"
>> +#include "btbcm.h"
>> +#include "btbcm_uart.h"
>>
>> struct bcm_data {
>> struct sk_buff *rx_skb;
>> struct sk_buff_head txq;
>> + struct hci_uart *hu;
>> +
>> + bool is_suspended; /* suspend/resume flag */
>> +
>> + struct timer_list timer; /* idle timer */
>> +
>> + struct btbcm_uart_parameters pars; /* device parameters */
>> + void *device_context; /* ACPI/DT device context */
>> };
>>
>> +/* Suspend/resume synchronization mutex */
>> +static DEFINE_MUTEX(plock);
>> +
>> +/*
>> + * Callbacks from the BCMBT_UART device
>> + */
>> +
>> +/*
>> + * The platform is suspending. Stop UART activity
>> + */
>> +static void suspend_notification(void *context)
>> +{
>> + struct ktermios ktermios;
>> + struct hci_uart *hu = (struct hci_uart *)context;
>> + struct bcm_data *h4 = hu->priv;
>> + struct tty_struct *tty = hu->tty;
>> + int status;
>> + unsigned int set = 0;
>> + unsigned int clear = 0;
>> +
>> + BT_DBG("suspend_notification with is_suspended %d", h4->is_suspended);
>> +
>> + if (!h4->pars.configure_sleep)
>> + return;
>> +
>> + if (!h4->is_suspended) {
>> + if (h4->pars.manual_fc) {
>> + /* Disable hardware flow control */
>> + ktermios = tty->termios;
>> + ktermios.c_cflag&= ~CRTSCTS;
>> + status = tty_set_termios(tty,&ktermios);
>> + if (status)
>> + BT_DBG("suspend_notification dis fc fail %d",
>> + status);
>> + else
>> + BT_DBG("suspend_notification hw fc disabled");
>> +
>> + /* Clear RTS to prevent the device from sending */
>> + /* (most PCs need OUT2 to enable interrupts) */
>> + status = tty->driver->ops->tiocmget(tty);
>> + BT_DBG("suspend_notification cur tiocm 0x%x", status);
>> + set&= ~(TIOCM_OUT2 | TIOCM_RTS);
>> + clear = ~set;
>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>> + if (status)
>> + BT_DBG("suspend_notification clr RTS fail %d",
>> + status);
>> + else
>> + BT_DBG("suspend_notification RTS cleared");
>> + status = tty->driver->ops->tiocmget(tty);
>> + BT_DBG("suspend_notification end tiocm 0x%x", status);
>> + }
>> +
>> + /* Once this callback returns, driver suspends BT via GPIO */
>> + h4->is_suspended = true;
>> + }
>> +}
>> +
>> +/*
>> + * The platform is resuming. Resume UART activity.
>> + */
>> +static void resume_notification(void *context)
>> +{
>> + struct ktermios ktermios;
>> + struct hci_uart *hu = (struct hci_uart *)context;
>> + struct bcm_data *h4 = hu->priv;
>> + struct tty_struct *tty = hu->tty;
>> + int status;
>> + unsigned int set = 0;
>> + unsigned int clear = 0;
>> +
>> + BT_DBG("resume_notification with is_suspended %d", h4->is_suspended);
>> +
>> + if (!h4->pars.configure_sleep)
>> + return;
>> +
>> + /* When this callback executes, the device has woken up already */
>> + if (h4->is_suspended) {
>> + h4->is_suspended = false;
>> +
>> + if (h4->pars.manual_fc) {
>> + status = tty->driver->ops->tiocmget(tty);
>> + BT_DBG("resume_notification cur tiocm 0x%x", status);
>> + set |= (TIOCM_OUT2 | TIOCM_RTS);
>> + clear = ~set;
>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>> + if (status)
>> + BT_DBG("resume_notification set RTS fail %d",
>> + status);
>> + else
>> + BT_DBG("resume_notification RTS set");
>> +
>> + /* Re-enable hardware flow control */
>> + ktermios = tty->termios;
>> + ktermios.c_cflag |= CRTSCTS;
>> + status = tty_set_termios(tty,&ktermios);
>> + if (status)
>> + BT_DBG("resume_notification enable fc fail %d",
>> + status);
>> + else
>> + BT_DBG("resume_notification hw fc re-enabled");
>> + }
>> + }
>> +
>> + /* If we're resumed, the idle timer must be running */
>> + status = mod_timer(&h4->timer, jiffies +
>> + msecs_to_jiffies(h4->pars.idle_timeout_in_secs * 1000));
>> +}
>> +
>> +/*
>> + * The BT device is resuming. Resume UART activity if suspended
>> + */
>> +static void wakeup_notification(void *context)
>> +{
>> + struct ktermios ktermios;
>> + struct hci_uart *hu = (struct hci_uart *)context;
>> + struct bcm_data *h4 = hu->priv;
>> + struct tty_struct *tty = hu->tty;
>> + int status;
>> + unsigned int set = 0;
>> + unsigned int clear = 0;
>> +
>> + if (!h4->pars.configure_sleep)
>> + return;
>> +
>> + status = tty->driver->ops->tiocmget(tty);
>> + BT_DBG("wakeup_notification hu %p current tiocm 0x%x", hu, status);
>> + if (h4->is_suspended) {
>> + if (h4->pars.manual_fc) {
>> + set |= (TIOCM_OUT2 | TIOCM_RTS);
>> + clear = ~set;
>> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
>> + TIOCM_OUT2 | TIOCM_LOOP;
>> + status = tty->driver->ops->tiocmset(tty, set, clear);
>> + if (status)
>> + BT_DBG("wakeup_notification set RTS fail %d",
>> + status);
>> + else
>> + BT_DBG("wakeup_notification RTS set");
>> +
>> + /* Re-enable hardware flow control */
>> + ktermios = tty->termios;
>> + ktermios.c_cflag |= CRTSCTS;
>> + status = tty_set_termios(tty,&ktermios);
>> + if (status)
>> + BT_DBG("wakeup_notification fc-en failure %d",
>> + status);
>> + else
>> + BT_DBG("wakeup_notification hw fc re-enabled");
>> + }
>> +
>> + h4->is_suspended = false;
>> + }
>> +
>> + /* If we're resumed, the idle timer must be running */
>> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>> + h4->pars.idle_timeout_in_secs * 1000));
>> +}
>> +
>> +/*
>> + * Make sure we're awake
>> + * (called when the resumed state is required)
>> + */
>> +static void bcm_ensure_wakeup(struct hci_uart *hu)
>> +{
>> + struct bcm_data *h4 = hu->priv;
>> + int status;
>> +
>> + if (!h4->pars.configure_sleep)
>> + return;
>> +
>> + /* Suspend/resume operations are serialized */
>> + mutex_lock(&plock);
>> +
>> + /* Nothing to do if resumed already */
>> + if (!h4->is_suspended) {
>> + mutex_unlock(&plock);
>> +
>> + /* Just reset the timer */
>> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>> + h4->pars.idle_timeout_in_secs * 1000));
>> + return;
>> + }
>> +
>> + /* Wakeup the device */
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_RESUME,
>> + h4->device_context, NULL, NULL);
>> + if (status)
>> + BT_DBG("bcm_ensure_wakeup failed to resume driver %d", status);
>> +
>> + /* Unflow control the port if configured */
>> + resume_notification(hu);
>> +
>> + mutex_unlock(&plock);
>> +}
>> +
>> +/*
>> + * Idle timer callback
>> + */
>> +static void bcm_idle_timeout(unsigned long arg)
>> +{
>> + struct hci_uart *hu = (struct hci_uart *)arg;
>> + struct bcm_data *h4 = hu->priv;
>> + int status;
>> +
>> + BT_DBG("bcm_idle_timeout hu %p", hu);
>> +
>> + /* Suspend/resume operations are serialized */
>> + mutex_lock(&plock);
>> +
>> + if (!h4->is_suspended) {
>> + /* Flow control the port if configured */
>> + suspend_notification(hu);
>> +
>> + /* Suspend the device */
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
>> + h4->device_context, NULL, NULL);
>> + if (status)
>> + BT_DBG("bcm_idle_timeout failed to suspend device %d",
>> + status);
>> + }
>> +
>> + mutex_unlock(&plock);
>> +}
>> +
>> static int bcm_open(struct hci_uart *hu)
>> {
>> - struct bcm_data *bcm;
>> + struct btbcm_uart_callbacks callbacks;
>> + unsigned long callbacks_size = sizeof(callbacks);
>> + int status;
>> + struct bcm_data *h4;
>> + struct tty_struct *tty = hu->tty;
>>
>> - BT_DBG("hu %p", hu);
>> + BT_DBG("bcm_h4_open hu %p", hu);
>>
>> - bcm = kzalloc(sizeof(*bcm), GFP_KERNEL);
>> - if (!bcm)
>> + h4 = kzalloc(sizeof(*h4), GFP_KERNEL);
>> + if (!h4)
>> return -ENOMEM;
>>
>> - skb_queue_head_init(&bcm->txq);
>> + skb_queue_head_init(&h4->txq);
>> + hu->priv = h4;
>> + h4->hu = hu;
>> + h4->is_suspended = false;
>> +
>> + /* Configure callbacks on the driver */
>> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
>> + callbacks.context = hu;
>> + strcpy(callbacks.name, tty->name);
>> + callbacks.p_suspend = suspend_notification;
>> + callbacks.p_resume = resume_notification;
>> + callbacks.p_wakeup = wakeup_notification;
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
>> + NULL,&callbacks,&callbacks_size);
>> + if (status) {
>> + BT_DBG("bcm_h4_open failed to set driver callbacks %d", status);
>> + return status;
>> + }
>> + if (callbacks_size != sizeof(void *)) {
>> + BT_DBG("bcm_h4_open got back %d bytes from callbacks?!",
>> + (int)callbacks_size);
>> + return -EMSGSIZE;
>> + }
>> + memcpy(&h4->device_context,&callbacks, sizeof(void *));
>> + BT_DBG("bcm_h4_open callbacks context %p", h4->device_context);
>> +
>> + /* Retrieve device parameters */
>> + callbacks_size = sizeof(h4->pars);
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_GET_PARAMETERS,
>> + h4->device_context,&h4->pars,
>> + &callbacks_size);
>> + if (status) {
>> + BT_DBG("bcm_h4_open failed to get dev parameters %d", status);
>> + return status;
>> + }
>> + BT_DBG("Pars ver %d csleep %d dalow %d balow %d idle %d",
>> + h4->pars.interface_version, h4->pars.configure_sleep,
>> + h4->pars.dev_wake_active_low, h4->pars.bt_wake_active_low,
>> + h4->pars.idle_timeout_in_secs);
>> +
>> + /* Cycle power to make sure the device is in the known state */
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
>> + h4->device_context, NULL, NULL);
>> + if (status) {
>> + BT_DBG("bcm_h4_open failed to power off %d", status);
>> + } else {
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_ON,
>> + h4->device_context, NULL, NULL);
>> + if (status)
>> + BT_DBG("bcm_h4_open failed to power on %d", status);
>> + }
>> +
>> + /* Start the idle timer */
>> + if (h4->pars.configure_sleep) {
>> + setup_timer(&h4->timer, bcm_idle_timeout, (unsigned long)hu);
>> + if (h4->pars.configure_sleep)
>> + mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
>> + h4->pars.idle_timeout_in_secs * 1000));
>> + }
>>
>> - hu->priv = bcm;
>> return 0;
>> }
>>
>> static int bcm_close(struct hci_uart *hu)
>> {
>> - struct bcm_data *bcm = hu->priv;
>> + struct btbcm_uart_callbacks callbacks;
>> + unsigned long callbacks_size = sizeof(callbacks);
>> + struct bcm_data *h4 = hu->priv;
>> + int status;
>>
>> - BT_DBG("hu %p", hu);
>> + hu->priv = NULL;
>>
>> - skb_queue_purge(&bcm->txq);
>> - kfree_skb(bcm->rx_skb);
>> - kfree(bcm);
>> + BT_DBG("bcm_h4_close hu %p", hu);
>> +
>> + /* If we're being closed, we must suspend */
>> + if (h4->pars.configure_sleep) {
>> + mutex_lock(&plock);
>> +
>> + if (!h4->is_suspended) {
>> + /* Flow control the port */
>> + suspend_notification(hu);
>> +
>> + /* Suspend the device */
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
>> + h4->device_context, NULL,
>> + NULL);
>> + if (status) {
>> + BT_DBG("bcm_h4_close suspend driver fail %d",
>> + status);
>> + }
>> + }
>> +
>> + mutex_unlock(&plock);
>> +
>> + del_timer_sync(&h4->timer);
>> + }
>> +
>> + /* Power off the device if possible */
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
>> + h4->device_context, NULL, NULL);
>> + if (status)
>> + BT_DBG("bcm_h4_close failed to power off %d", status);
>> +
>> + /* de-configure callbacks on the driver */
>> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
>> + callbacks.context = hu;
>> + callbacks.p_suspend = NULL;
>> + callbacks.p_resume = NULL;
>> + callbacks.p_wakeup = NULL;
>> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
>> + h4->device_context,&callbacks,
>> + &callbacks_size);
>> + if (status)
>> + BT_DBG("bcm_h4_close failed to reset drv callbacks %d", status);
>> + skb_queue_purge(&h4->txq);
>>
>> hu->priv = NULL;
>> + kfree(h4);
>> +
>> return 0;
>> }
>>
>> @@ -79,11 +445,137 @@ static int bcm_flush(struct hci_uart *hu)
>>
>> static int bcm_setup(struct hci_uart *hu)
>> {
>> - BT_DBG("hu %p", hu);
>> + struct bcm_data *h4 = hu->priv;
>> + int status;
>> + struct sk_buff *skb;
>> + unsigned char sleep_pars[] = {
>> + 0x01, /* sleep mode 1=UART */
>> + 0x02, /* idle threshold HOST (value * 300ms) */
>> + 0x02, /* idle threshold HC (value * 300ms) */
>> + 0x01, /* BT_WAKE active mode - 1=active high, 0 = active low */
>> + 0x00, /* HOST_WAKE active mode - 1=active high, 0 = active low */
>> + 0x01, /* Allow host sleep during SCO - FALSE */
>> + 0x01, /* combine sleep mode and LPM - 1 == TRUE */
>> + 0x00, /* enable tristate control of UART TX line - FALSE */
>> + 0x00, /* USB auto-sleep on USB SUSPEND */
>> + 0x00, /* USB USB RESUME timeout (seconds) */
>> + 0x00, /* Pulsed Host Wake */
>> + 0x00 /* Enable Break To Host */
>> + };
>> + unsigned char pcm_int_pars[] = {
>> + 0x00, /* 0=PCM routing, 1=SCO over HCI */
>> + 0x02, /* 0=128Kbps,1=256Kbps,2=512Kbps,3=1024Kbps,4=2048Kbps */
>> + 0x00, /* short frame sync 0=short, 1=long */
>> + 0x00, /* sync mode 0=slave, 1=master */
>> + 0x00 /* clock mode 0=slave, 1=master */
>> + };
>> + unsigned char pcm_format_pars[] = {
>> + 0x00, /* LSB_First 1=TRUE, 0=FALSE */
>> + 0x00, /* Fill_Value (use 0-7 for fill bits value) */
>> + 0x02, /* Fill_Method (2=sign extended) */
>> + 0x03, /* Fill_Num # of fill bits 0-3) */
>> + 0x01 /* Right_Justify 1=TRUE, 0=FALSE */
>> + };
>> + unsigned char time_slot_number = 0;
>> +
>> + BT_DBG("bcm_h4_setup hu %p", hu);
>> +
>> + /* Bring the UART into known default state */
>> + status = btbcm_init_uart(hu);
>> + if (status) {
>> + BT_DBG("bcm_h4_setup failed to init BT device %d", status);
>> + return status;
>> + }
>> +
>> + /* Basic sanity check */
>> + skb = __hci_cmd_sync(hu->hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT);
>> + if (IS_ERR(skb)) {
>> + status = PTR_ERR(skb);
>> + BT_ERR("bcm_h4_setup HCI Reset failed (%d)", status);
>> + return status;
>> + }
>> + kfree_skb(skb);
>> + BT_DBG("bcm_h4_setup HCI Reset succeeded");
>> +
>> + /* Set the new baud rate */
>> + status = btbcm_set_baud_rate(hu,
>> + h4->pars.baud_rate_before_config_download);
>> + if (status) {
>> + BT_ERR("bcm_h4_setup set_baud_rate faiilure %d", status);
>> + return status;
>> + }
>>
>> hu->hdev->set_bdaddr = btbcm_set_bdaddr;
>>
>> - return btbcm_setup_patchram(hu->hdev);
>> + /* Download the firmware and reconfigure the UART afterwards */
>> + status = btbcm_setup_patchram(hu->hdev);
>> + if (status) {
>> + BT_ERR("bcm_h4_setup setup_patchram faiilure %d", status);
>> + return status;
>> + }
>> +
>> + /* Configure SCO PCM parameters */
>> + if (h4->pars.configure_audio) {
>> + pcm_int_pars[0] = h4->pars.PCMRouting;
>> + pcm_int_pars[1] = h4->pars.PCMInCallBitclock;
>> + pcm_int_pars[2] = h4->pars.PCMShortFrameSync;
>> + pcm_int_pars[3] = h4->pars.PCMSyncMode;
>> + pcm_int_pars[4] = h4->pars.PCMClockMode;
>> + skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_pars),
>
> Guess 0xfc1c is the hci command. Can you create a define for them?

in a lot of cases we kept the hex number and just included a proper comment above the command to explain what this command does. That is at least how we have done it inside the Intel vendor support. I am fine with also creating a define for it. However sometimes they got heavily complicated and long.

Regards

Marcel


2015-05-19 12:47:46

by Arend van Spriel

[permalink] [raw]
Subject: Re: [RFC v3 4/4] BlueZ Broadcom UART Protocol

On 05/13/15 23:50, Ilya Faenson wrote:
> Enhance Broadcom protocol with the UART setup, firmware download
> and power management.
>
> Signed-off-by: Ilya Faenson<[email protected]>
> ---
> drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 513 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
> index 1ec0b4a..ccd92ed 100644
> --- a/drivers/bluetooth/hci_bcm.c
> +++ b/drivers/bluetooth/hci_bcm.c
> @@ -1,8 +1,9 @@
> /*
> *
> - * Bluetooth HCI UART driver for Broadcom devices
> + * Bluetooth UART H4 protocol for Broadcom devices
> *
> * Copyright (C) 2015 Intel Corporation
> + * Copyright (C) 2015 Broadcom Corporation
> *
> *
> * This program is free software; you can redistribute it and/or modify
> @@ -21,48 +22,413 @@
> *
> */
>
> +#include<linux/module.h>
> #include<linux/kernel.h>
> +#include<linux/init.h>
> +#include<linux/types.h>
> +#include<linux/fcntl.h>
> +#include<linux/interrupt.h>
> +#include<linux/ptrace.h>
> +#include<linux/poll.h>
> +#include<linux/slab.h>
> +#include<linux/tty.h>
> #include<linux/errno.h>
> +#include<linux/string.h>
> +#include<linux/signal.h>
> +#include<linux/ioctl.h>
> #include<linux/skbuff.h>
> +#include<linux/gpio/consumer.h>
> +#include<linux/of_gpio.h>
> +#include<linux/of_platform.h>
>
> #include<net/bluetooth/bluetooth.h>
> #include<net/bluetooth/hci_core.h>
>
> -#include "btbcm.h"
> #include "hci_uart.h"
> +#include "btbcm.h"
> +#include "btbcm_uart.h"
>
> struct bcm_data {
> struct sk_buff *rx_skb;
> struct sk_buff_head txq;
> + struct hci_uart *hu;
> +
> + bool is_suspended; /* suspend/resume flag */
> +
> + struct timer_list timer; /* idle timer */
> +
> + struct btbcm_uart_parameters pars; /* device parameters */
> + void *device_context; /* ACPI/DT device context */
> };
>
> +/* Suspend/resume synchronization mutex */
> +static DEFINE_MUTEX(plock);
> +
> +/*
> + * Callbacks from the BCMBT_UART device
> + */
> +
> +/*
> + * The platform is suspending. Stop UART activity
> + */
> +static void suspend_notification(void *context)
> +{
> + struct ktermios ktermios;
> + struct hci_uart *hu = (struct hci_uart *)context;
> + struct bcm_data *h4 = hu->priv;
> + struct tty_struct *tty = hu->tty;
> + int status;
> + unsigned int set = 0;
> + unsigned int clear = 0;
> +
> + BT_DBG("suspend_notification with is_suspended %d", h4->is_suspended);
> +
> + if (!h4->pars.configure_sleep)
> + return;
> +
> + if (!h4->is_suspended) {
> + if (h4->pars.manual_fc) {
> + /* Disable hardware flow control */
> + ktermios = tty->termios;
> + ktermios.c_cflag&= ~CRTSCTS;
> + status = tty_set_termios(tty,&ktermios);
> + if (status)
> + BT_DBG("suspend_notification dis fc fail %d",
> + status);
> + else
> + BT_DBG("suspend_notification hw fc disabled");
> +
> + /* Clear RTS to prevent the device from sending */
> + /* (most PCs need OUT2 to enable interrupts) */
> + status = tty->driver->ops->tiocmget(tty);
> + BT_DBG("suspend_notification cur tiocm 0x%x", status);
> + set&= ~(TIOCM_OUT2 | TIOCM_RTS);
> + clear = ~set;
> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + status = tty->driver->ops->tiocmset(tty, set, clear);
> + if (status)
> + BT_DBG("suspend_notification clr RTS fail %d",
> + status);
> + else
> + BT_DBG("suspend_notification RTS cleared");
> + status = tty->driver->ops->tiocmget(tty);
> + BT_DBG("suspend_notification end tiocm 0x%x", status);
> + }
> +
> + /* Once this callback returns, driver suspends BT via GPIO */
> + h4->is_suspended = true;
> + }
> +}
> +
> +/*
> + * The platform is resuming. Resume UART activity.
> + */
> +static void resume_notification(void *context)
> +{
> + struct ktermios ktermios;
> + struct hci_uart *hu = (struct hci_uart *)context;
> + struct bcm_data *h4 = hu->priv;
> + struct tty_struct *tty = hu->tty;
> + int status;
> + unsigned int set = 0;
> + unsigned int clear = 0;
> +
> + BT_DBG("resume_notification with is_suspended %d", h4->is_suspended);
> +
> + if (!h4->pars.configure_sleep)
> + return;
> +
> + /* When this callback executes, the device has woken up already */
> + if (h4->is_suspended) {
> + h4->is_suspended = false;
> +
> + if (h4->pars.manual_fc) {
> + status = tty->driver->ops->tiocmget(tty);
> + BT_DBG("resume_notification cur tiocm 0x%x", status);
> + set |= (TIOCM_OUT2 | TIOCM_RTS);
> + clear = ~set;
> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + status = tty->driver->ops->tiocmset(tty, set, clear);
> + if (status)
> + BT_DBG("resume_notification set RTS fail %d",
> + status);
> + else
> + BT_DBG("resume_notification RTS set");
> +
> + /* Re-enable hardware flow control */
> + ktermios = tty->termios;
> + ktermios.c_cflag |= CRTSCTS;
> + status = tty_set_termios(tty,&ktermios);
> + if (status)
> + BT_DBG("resume_notification enable fc fail %d",
> + status);
> + else
> + BT_DBG("resume_notification hw fc re-enabled");
> + }
> + }
> +
> + /* If we're resumed, the idle timer must be running */
> + status = mod_timer(&h4->timer, jiffies +
> + msecs_to_jiffies(h4->pars.idle_timeout_in_secs * 1000));
> +}
> +
> +/*
> + * The BT device is resuming. Resume UART activity if suspended
> + */
> +static void wakeup_notification(void *context)
> +{
> + struct ktermios ktermios;
> + struct hci_uart *hu = (struct hci_uart *)context;
> + struct bcm_data *h4 = hu->priv;
> + struct tty_struct *tty = hu->tty;
> + int status;
> + unsigned int set = 0;
> + unsigned int clear = 0;
> +
> + if (!h4->pars.configure_sleep)
> + return;
> +
> + status = tty->driver->ops->tiocmget(tty);
> + BT_DBG("wakeup_notification hu %p current tiocm 0x%x", hu, status);
> + if (h4->is_suspended) {
> + if (h4->pars.manual_fc) {
> + set |= (TIOCM_OUT2 | TIOCM_RTS);
> + clear = ~set;
> + set&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + clear&= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
> + TIOCM_OUT2 | TIOCM_LOOP;
> + status = tty->driver->ops->tiocmset(tty, set, clear);
> + if (status)
> + BT_DBG("wakeup_notification set RTS fail %d",
> + status);
> + else
> + BT_DBG("wakeup_notification RTS set");
> +
> + /* Re-enable hardware flow control */
> + ktermios = tty->termios;
> + ktermios.c_cflag |= CRTSCTS;
> + status = tty_set_termios(tty,&ktermios);
> + if (status)
> + BT_DBG("wakeup_notification fc-en failure %d",
> + status);
> + else
> + BT_DBG("wakeup_notification hw fc re-enabled");
> + }
> +
> + h4->is_suspended = false;
> + }
> +
> + /* If we're resumed, the idle timer must be running */
> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
> + h4->pars.idle_timeout_in_secs * 1000));
> +}
> +
> +/*
> + * Make sure we're awake
> + * (called when the resumed state is required)
> + */
> +static void bcm_ensure_wakeup(struct hci_uart *hu)
> +{
> + struct bcm_data *h4 = hu->priv;
> + int status;
> +
> + if (!h4->pars.configure_sleep)
> + return;
> +
> + /* Suspend/resume operations are serialized */
> + mutex_lock(&plock);
> +
> + /* Nothing to do if resumed already */
> + if (!h4->is_suspended) {
> + mutex_unlock(&plock);
> +
> + /* Just reset the timer */
> + status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
> + h4->pars.idle_timeout_in_secs * 1000));
> + return;
> + }
> +
> + /* Wakeup the device */
> + status = btbcm_uart_control(BTBCM_UART_ACTION_RESUME,
> + h4->device_context, NULL, NULL);
> + if (status)
> + BT_DBG("bcm_ensure_wakeup failed to resume driver %d", status);
> +
> + /* Unflow control the port if configured */
> + resume_notification(hu);
> +
> + mutex_unlock(&plock);
> +}
> +
> +/*
> + * Idle timer callback
> + */
> +static void bcm_idle_timeout(unsigned long arg)
> +{
> + struct hci_uart *hu = (struct hci_uart *)arg;
> + struct bcm_data *h4 = hu->priv;
> + int status;
> +
> + BT_DBG("bcm_idle_timeout hu %p", hu);
> +
> + /* Suspend/resume operations are serialized */
> + mutex_lock(&plock);
> +
> + if (!h4->is_suspended) {
> + /* Flow control the port if configured */
> + suspend_notification(hu);
> +
> + /* Suspend the device */
> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
> + h4->device_context, NULL, NULL);
> + if (status)
> + BT_DBG("bcm_idle_timeout failed to suspend device %d",
> + status);
> + }
> +
> + mutex_unlock(&plock);
> +}
> +
> static int bcm_open(struct hci_uart *hu)
> {
> - struct bcm_data *bcm;
> + struct btbcm_uart_callbacks callbacks;
> + unsigned long callbacks_size = sizeof(callbacks);
> + int status;
> + struct bcm_data *h4;
> + struct tty_struct *tty = hu->tty;
>
> - BT_DBG("hu %p", hu);
> + BT_DBG("bcm_h4_open hu %p", hu);
>
> - bcm = kzalloc(sizeof(*bcm), GFP_KERNEL);
> - if (!bcm)
> + h4 = kzalloc(sizeof(*h4), GFP_KERNEL);
> + if (!h4)
> return -ENOMEM;
>
> - skb_queue_head_init(&bcm->txq);
> + skb_queue_head_init(&h4->txq);
> + hu->priv = h4;
> + h4->hu = hu;
> + h4->is_suspended = false;
> +
> + /* Configure callbacks on the driver */
> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
> + callbacks.context = hu;
> + strcpy(callbacks.name, tty->name);
> + callbacks.p_suspend = suspend_notification;
> + callbacks.p_resume = resume_notification;
> + callbacks.p_wakeup = wakeup_notification;
> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
> + NULL,&callbacks,&callbacks_size);
> + if (status) {
> + BT_DBG("bcm_h4_open failed to set driver callbacks %d", status);
> + return status;
> + }
> + if (callbacks_size != sizeof(void *)) {
> + BT_DBG("bcm_h4_open got back %d bytes from callbacks?!",
> + (int)callbacks_size);
> + return -EMSGSIZE;
> + }
> + memcpy(&h4->device_context,&callbacks, sizeof(void *));
> + BT_DBG("bcm_h4_open callbacks context %p", h4->device_context);
> +
> + /* Retrieve device parameters */
> + callbacks_size = sizeof(h4->pars);
> + status = btbcm_uart_control(BTBCM_UART_ACTION_GET_PARAMETERS,
> + h4->device_context,&h4->pars,
> + &callbacks_size);
> + if (status) {
> + BT_DBG("bcm_h4_open failed to get dev parameters %d", status);
> + return status;
> + }
> + BT_DBG("Pars ver %d csleep %d dalow %d balow %d idle %d",
> + h4->pars.interface_version, h4->pars.configure_sleep,
> + h4->pars.dev_wake_active_low, h4->pars.bt_wake_active_low,
> + h4->pars.idle_timeout_in_secs);
> +
> + /* Cycle power to make sure the device is in the known state */
> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
> + h4->device_context, NULL, NULL);
> + if (status) {
> + BT_DBG("bcm_h4_open failed to power off %d", status);
> + } else {
> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_ON,
> + h4->device_context, NULL, NULL);
> + if (status)
> + BT_DBG("bcm_h4_open failed to power on %d", status);
> + }
> +
> + /* Start the idle timer */
> + if (h4->pars.configure_sleep) {
> + setup_timer(&h4->timer, bcm_idle_timeout, (unsigned long)hu);
> + if (h4->pars.configure_sleep)
> + mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
> + h4->pars.idle_timeout_in_secs * 1000));
> + }
>
> - hu->priv = bcm;
> return 0;
> }
>
> static int bcm_close(struct hci_uart *hu)
> {
> - struct bcm_data *bcm = hu->priv;
> + struct btbcm_uart_callbacks callbacks;
> + unsigned long callbacks_size = sizeof(callbacks);
> + struct bcm_data *h4 = hu->priv;
> + int status;
>
> - BT_DBG("hu %p", hu);
> + hu->priv = NULL;
>
> - skb_queue_purge(&bcm->txq);
> - kfree_skb(bcm->rx_skb);
> - kfree(bcm);
> + BT_DBG("bcm_h4_close hu %p", hu);
> +
> + /* If we're being closed, we must suspend */
> + if (h4->pars.configure_sleep) {
> + mutex_lock(&plock);
> +
> + if (!h4->is_suspended) {
> + /* Flow control the port */
> + suspend_notification(hu);
> +
> + /* Suspend the device */
> + status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
> + h4->device_context, NULL,
> + NULL);
> + if (status) {
> + BT_DBG("bcm_h4_close suspend driver fail %d",
> + status);
> + }
> + }
> +
> + mutex_unlock(&plock);
> +
> + del_timer_sync(&h4->timer);
> + }
> +
> + /* Power off the device if possible */
> + status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
> + h4->device_context, NULL, NULL);
> + if (status)
> + BT_DBG("bcm_h4_close failed to power off %d", status);
> +
> + /* de-configure callbacks on the driver */
> + callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
> + callbacks.context = hu;
> + callbacks.p_suspend = NULL;
> + callbacks.p_resume = NULL;
> + callbacks.p_wakeup = NULL;
> + status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
> + h4->device_context,&callbacks,
> + &callbacks_size);
> + if (status)
> + BT_DBG("bcm_h4_close failed to reset drv callbacks %d", status);
> + skb_queue_purge(&h4->txq);
>
> hu->priv = NULL;
> + kfree(h4);
> +
> return 0;
> }
>
> @@ -79,11 +445,137 @@ static int bcm_flush(struct hci_uart *hu)
>
> static int bcm_setup(struct hci_uart *hu)
> {
> - BT_DBG("hu %p", hu);
> + struct bcm_data *h4 = hu->priv;
> + int status;
> + struct sk_buff *skb;
> + unsigned char sleep_pars[] = {
> + 0x01, /* sleep mode 1=UART */
> + 0x02, /* idle threshold HOST (value * 300ms) */
> + 0x02, /* idle threshold HC (value * 300ms) */
> + 0x01, /* BT_WAKE active mode - 1=active high, 0 = active low */
> + 0x00, /* HOST_WAKE active mode - 1=active high, 0 = active low */
> + 0x01, /* Allow host sleep during SCO - FALSE */
> + 0x01, /* combine sleep mode and LPM - 1 == TRUE */
> + 0x00, /* enable tristate control of UART TX line - FALSE */
> + 0x00, /* USB auto-sleep on USB SUSPEND */
> + 0x00, /* USB USB RESUME timeout (seconds) */
> + 0x00, /* Pulsed Host Wake */
> + 0x00 /* Enable Break To Host */
> + };
> + unsigned char pcm_int_pars[] = {
> + 0x00, /* 0=PCM routing, 1=SCO over HCI */
> + 0x02, /* 0=128Kbps,1=256Kbps,2=512Kbps,3=1024Kbps,4=2048Kbps */
> + 0x00, /* short frame sync 0=short, 1=long */
> + 0x00, /* sync mode 0=slave, 1=master */
> + 0x00 /* clock mode 0=slave, 1=master */
> + };
> + unsigned char pcm_format_pars[] = {
> + 0x00, /* LSB_First 1=TRUE, 0=FALSE */
> + 0x00, /* Fill_Value (use 0-7 for fill bits value) */
> + 0x02, /* Fill_Method (2=sign extended) */
> + 0x03, /* Fill_Num # of fill bits 0-3) */
> + 0x01 /* Right_Justify 1=TRUE, 0=FALSE */
> + };
> + unsigned char time_slot_number = 0;
> +
> + BT_DBG("bcm_h4_setup hu %p", hu);
> +
> + /* Bring the UART into known default state */
> + status = btbcm_init_uart(hu);
> + if (status) {
> + BT_DBG("bcm_h4_setup failed to init BT device %d", status);
> + return status;
> + }
> +
> + /* Basic sanity check */
> + skb = __hci_cmd_sync(hu->hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + status = PTR_ERR(skb);
> + BT_ERR("bcm_h4_setup HCI Reset failed (%d)", status);
> + return status;
> + }
> + kfree_skb(skb);
> + BT_DBG("bcm_h4_setup HCI Reset succeeded");
> +
> + /* Set the new baud rate */
> + status = btbcm_set_baud_rate(hu,
> + h4->pars.baud_rate_before_config_download);
> + if (status) {
> + BT_ERR("bcm_h4_setup set_baud_rate faiilure %d", status);
> + return status;
> + }
>
> hu->hdev->set_bdaddr = btbcm_set_bdaddr;
>
> - return btbcm_setup_patchram(hu->hdev);
> + /* Download the firmware and reconfigure the UART afterwards */
> + status = btbcm_setup_patchram(hu->hdev);
> + if (status) {
> + BT_ERR("bcm_h4_setup setup_patchram faiilure %d", status);
> + return status;
> + }
> +
> + /* Configure SCO PCM parameters */
> + if (h4->pars.configure_audio) {
> + pcm_int_pars[0] = h4->pars.PCMRouting;
> + pcm_int_pars[1] = h4->pars.PCMInCallBitclock;
> + pcm_int_pars[2] = h4->pars.PCMShortFrameSync;
> + pcm_int_pars[3] = h4->pars.PCMSyncMode;
> + pcm_int_pars[4] = h4->pars.PCMClockMode;
> + skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_pars),

Guess 0xfc1c is the hci command. Can you create a define for them?

Regards,
Arend

> + pcm_int_pars, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + status = PTR_ERR(skb);
> + BT_ERR("bcm_h4_setup PCM INT VSC failed (%d)", status);
> + return status;
> + }
> + kfree_skb(skb);
> + BT_DBG("bcm_h4_setup PCM INT Parameters VSC succeeded");
> +
> + pcm_format_pars[0] = h4->pars.PCMLSBFirst;
> + pcm_format_pars[1] = h4->pars.PCMFillValue;
> + pcm_format_pars[2] = h4->pars.PCMFillMethod;
> + pcm_format_pars[3] = h4->pars.PCMFillNum;
> + pcm_format_pars[4] = h4->pars.PCMRightJustify;
> + skb = __hci_cmd_sync(hu->hdev, 0xfc1e, sizeof(pcm_format_pars),
> + pcm_format_pars, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + status = PTR_ERR(skb);
> + BT_ERR("bcm_h4_setup PCM Format VSC failed (%d)",
> + status);
> + return status;
> + }
> + kfree_skb(skb);
> + BT_DBG("bcm_h4_setup PCM Format VSC succeeded");
> +
> + skb = __hci_cmd_sync(hu->hdev, 0xfc22, sizeof(time_slot_number),
> + &time_slot_number, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + status = PTR_ERR(skb);
> + BT_ERR("bcm_h4_setup SCO Time Slot VSC failed (%d)",
> + status);
> + return status;
> + }
> + kfree_skb(skb);
> + BT_DBG("bcm_h4_setup SCO Time Slot VSC succeeded");
> + }
> +
> + /* Configure device's suspend/resume operation */
> + if (h4->pars.configure_sleep) {
> + /* Override the default */
> + sleep_pars[3] = (unsigned char)!h4->pars.bt_wake_active_low;
> + sleep_pars[4] = (unsigned char)!h4->pars.dev_wake_active_low;
> + skb = __hci_cmd_sync(hu->hdev, 0xfc27, sizeof(sleep_pars),
> + sleep_pars, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + status = PTR_ERR(skb);
> + BT_ERR("bcm_h4_setup Sleep VSC failed (%d)", status);
> + return status;
> + }
> + kfree_skb(skb);
> + BT_DBG("bcm_h4_setup Set Sleep Parameters VSC succeeded");
> + }
> +
> + return 0;
> }
>
> static const struct h4_recv_pkt bcm_recv_pkts[] = {
> @@ -99,6 +591,9 @@ static int bcm_recv(struct hci_uart *hu, const void *data, int count)
> if (!test_bit(HCI_UART_REGISTERED,&hu->flags))
> return -EUNATCH;
>
> + /* Make sure we're resumed */
> + bcm_ensure_wakeup(hu);
> +
> bcm->rx_skb = h4_recv_buf(hu->hdev, bcm->rx_skb, data, count,
> bcm_recv_pkts, ARRAY_SIZE(bcm_recv_pkts));
> if (IS_ERR(bcm->rx_skb)) {
> @@ -116,6 +611,9 @@ static int bcm_enqueue(struct hci_uart *hu, struct sk_buff *skb)
>
> BT_DBG("hu %p skb %p", hu, skb);
>
> + /* Make sure we're resumed */
> + bcm_ensure_wakeup(hu);
> +
> /* Prepend skb with frame type */
> memcpy(skb_push(skb, 1),&bt_cb(skb)->pkt_type, 1);
> skb_queue_tail(&bcm->txq, skb);

2015-05-19 12:42:05

by Arend van Spriel

[permalink] [raw]
Subject: Re: [RFC v3 2/4] Broadcom Bluetooth UART Platform Driver

On 05/13/15 23:50, Ilya Faenson wrote:
> Introduce the device tree enumerated Broadcom Bluetooth UART driver.
>
> Signed-off-by: Ilya Faenson<[email protected]>
> ---
> drivers/bluetooth/Kconfig | 9 +
> drivers/bluetooth/Makefile | 1 +
> drivers/bluetooth/btbcm_uart.c | 705 +++++++++++++++++++++++++++++++++++++++++
> drivers/bluetooth/btbcm_uart.h | 116 +++++++
> 4 files changed, 831 insertions(+)
> create mode 100644 drivers/bluetooth/btbcm_uart.c
> create mode 100644 drivers/bluetooth/btbcm_uart.h
>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index ed5c273..7127ada 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -128,6 +128,7 @@ config BT_HCIUART_BCM
> bool "Broadcom protocol support"
> depends on BT_HCIUART
> select BT_HCIUART_H4
> + select BT_UART_BCM
> select BT_BCM
> help
> The Broadcom protocol support enables Bluetooth HCI over serial
> @@ -135,6 +136,14 @@ config BT_HCIUART_BCM
>
> Say Y here to compile support for Broadcom protocol.
>
> +config BT_UART_BCM
> + tristate "Broadcom BT UART driver"
> + depends on BT_HCIUART_H4&& TTY
> + help
> + This driver supports the HCI_UART_BT protocol.
> +
> + It manages Bluetooth UART device properties and GPIOs.
> +
> config BT_HCIBCM203X
> tristate "HCI BCM203x USB driver"
> depends on USB
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index dd0d9c4..0e5fd66 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_BT_MRVL) += btmrvl.o
> obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
> obj-$(CONFIG_BT_WILINK) += btwilink.o
> obj-$(CONFIG_BT_BCM) += btbcm.o
> +obj-$(CONFIG_BT_UART_BCM) += btbcm_uart.o
>
> btmrvl-y := btmrvl_main.o
> btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
> diff --git a/drivers/bluetooth/btbcm_uart.c b/drivers/bluetooth/btbcm_uart.c
> new file mode 100644
> index 0000000..2afaed1
> --- /dev/null
> +++ b/drivers/bluetooth/btbcm_uart.c
> @@ -0,0 +1,705 @@
> +/*
> + * Bluetooth BCM UART Driver
> + *
> + * Copyright (c) 2015 Broadcom Corporation
> + *
> + * This file is provided under a dual BSD/GPLv2 license. When using or
> + * redistributing this file, you may do so under either license.
> + *
> + * GPL LICENSE SUMMARY
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + * 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.
> + *
> + * BSD LICENSE SUMMARY
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in
> + * the documentation and/or other materials provided with the
> + * distribution.
> + * Neither the name of Intel Corporation nor the names of its
> + * contributors may be used to endorse or promote products derived
> + * from this software without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include<linux/module.h>
> +
> +#include<linux/kernel.h>
> +#include<linux/init.h>
> +#include<linux/types.h>
> +#include<linux/fcntl.h>
> +#include<linux/interrupt.h>
> +#include<linux/ptrace.h>
> +#include<linux/poll.h>
> +
> +#include<linux/slab.h>
> +#include<linux/tty.h>
> +#include<linux/errno.h>
> +#include<linux/string.h>
> +#include<linux/signal.h>
> +#include<linux/ioctl.h>
> +#include<linux/skbuff.h>
> +#include<linux/list.h>
> +
> +#include<net/bluetooth/bluetooth.h>
> +#include<net/bluetooth/hci_core.h>
> +
> +#include<linux/gpio/consumer.h>
> +#include<linux/of.h>
> +#include<linux/of_gpio.h>
> +#include<linux/of_platform.h>
> +
> +#include "btbcm_uart.h"
> +
> +static int idle_timeout = 5;
> +module_param(idle_timeout, int, 0);
> +MODULE_PARM_DESC(idle_timeout, "Bluetooth idle timeout in seconds");
> +
> +/* Device context */
> +struct bcm_device {
> + struct list_head list;
> +
> + struct platform_device *pdev;
> + struct gpio_desc *bt_wake_gpio;
> + struct gpio_desc *dev_wake_gpio;
> + struct gpio_desc *reg_on_gpio;
> + int bt_wake_irq;
> + int dev_wake_active_low;
> + int reg_on_active_low;
> + int bt_wake_active_low;
> + u32 configure_sleep;
> + u32 manual_fc;
> + u32 baud_rate_before_config_download;
> + u32 configure_audio;
> + u32 PCMClockMode;
> + u32 PCMFillMethod;
> + u32 PCMFillNum;
> + u32 PCMFillValue;
> + u32 PCMInCallBitclock;
> + u32 PCMLSBFirst;
> + u32 PCMRightJustify;
> + u32 PCMRouting;
> + u32 PCMShortFrameSync;
> + u32 PCMSyncMode;
> +
> + char tty_name[64];
> +
> + struct btbcm_uart_callbacks protocol_callbacks;
> + struct work_struct wakeup_work;
> +};
> +
> +/* List of BCM BT UART devices */
> +static DEFINE_SPINLOCK(device_list_lock);
> +static LIST_HEAD(device_list);
> +
> +/*
> + * Calling the BCM protocol at lower execution priority
> + */
> +static void bcm_bt_wakeup_task(struct work_struct *ws)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device =
> + container_of(ws, struct bcm_device, wakeup_work);
> +
> + if (!p_bcm_device) {
> + BT_DBG("bcm_bt_wakeup_task - failing, no device");

Could consider using "%s - ...", __func__ in BT_DBG() macros.

> + return;
> + }
> +
> + /* Make sure the device is resumed */
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
> + BT_DBG("bcm_bt_wakeup_task - resume %d written, delaying 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> +
> + /* Let the protocol know it's time to wake up */
> + if (p_bcm_device->protocol_callbacks.p_wakeup)
> + p_bcm_device->protocol_callbacks.p_wakeup(
> + p_bcm_device->protocol_callbacks.context);
> +}
> +
> +/*
> + * Interrupt routine for the wake from the device
> + */
> +static irqreturn_t bcm_bt_uart_isr(int irq, void *context)
> +{
> + unsigned int bt_wake;
> + struct bcm_device *p = (struct bcm_device *)context;
> +
> + bt_wake = gpiod_get_value(p->bt_wake_gpio);
> + BT_DBG("bcm_bt_uart_isr with bt_wake of %d (active_low %d), req bh",
> + bt_wake, p->bt_wake_active_low);
> +
> + /* Defer the actual processing to the platform work queue */
> + schedule_work(&p->wakeup_work);
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * Device instance startup
> + */
> +static int bcm_bt_uart_probe(struct platform_device *pdev)
> +{
> + int ret = 0;
> + struct device_node *np = pdev->dev.of_node;
> + const char *tty_name;
> + struct bcm_device *p_bcm_device = NULL;
> +
> + p_bcm_device = devm_kzalloc(&pdev->dev, sizeof(*p_bcm_device),
> + GFP_KERNEL);
> + if (!p_bcm_device) {
> + BT_DBG("bcm_bt_uart_probe - failing due to no memory");
> + return -ENOMEM;
> + }
> + p_bcm_device->pdev = pdev;
> + BT_DBG("bcm_bt_uart_probe %p context", p_bcm_device);
> +
> + /* Get dev wake GPIO */
> + p_bcm_device->dev_wake_gpio = gpiod_get(&pdev->dev, "bt-wake");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-wake returned %p",
> + p_bcm_device->dev_wake_gpio);
> + if (IS_ERR(p_bcm_device->dev_wake_gpio)) {
> + ret = PTR_ERR(p_bcm_device->dev_wake_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - dev_wake GPIO: %d\n", ret);
> + }
> + p_bcm_device->dev_wake_gpio = NULL;
> + } else {
> + int resume_flag;
> +
> + p_bcm_device->dev_wake_active_low = gpiod_is_active_low
> + (p_bcm_device->dev_wake_gpio);
> + BT_DBG("bcm_bt_uart_probe - dev_wake a-low is %d (cans %d)",
> + p_bcm_device->dev_wake_active_low,
> + gpiod_cansleep(p_bcm_device->dev_wake_gpio));
> +
> + /* configure dev_wake as output with init resumed state */
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + ret = gpiod_direction_output(p_bcm_device->dev_wake_gpio,
> + resume_flag);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s dev_wake GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + goto end;
> + } else {
> + BT_DBG("bcm_bt_uart_probe - dev_wake set to %d",
> + resume_flag);
> + }
> + }
> +
> + /* Get power on/off GPIO */
> + p_bcm_device->reg_on_gpio = gpiod_get(&pdev->dev, "reg-on");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for reg-on returned %p",
> + p_bcm_device->reg_on_gpio);
> + if (IS_ERR(p_bcm_device->reg_on_gpio)) {
> + ret = PTR_ERR(p_bcm_device->reg_on_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - reg_on GPIO: %d\n", ret);
> + }
> + p_bcm_device->reg_on_gpio = NULL;
> + } else {
> + int poweron_flag;
> +
> + p_bcm_device->reg_on_active_low = gpiod_is_active_low
> + (p_bcm_device->reg_on_gpio);
> + BT_DBG("bcm_bt_uart_probe - reg_on a-low is %d (cans %d)",
> + p_bcm_device->reg_on_active_low,
> + gpiod_cansleep(p_bcm_device->reg_on_gpio));
> +
> + /* configure reg_on as output with init on state */
> + poweron_flag = !p_bcm_device->reg_on_active_low;
> + ret = gpiod_direction_output(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s reg_on GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + } else {
> + BT_DBG("bcm_bt_uart_probe - reg_on initially set to %d",
> + poweron_flag);
> + }
> + }
> +
> + platform_set_drvdata(pdev, p_bcm_device);
> + /* Must be done before interrupt is requested */
> + INIT_WORK(&p_bcm_device->wakeup_work, bcm_bt_wakeup_task);
> +
> + /* Get bt host wake GPIO */
> + p_bcm_device->bt_wake_gpio = gpiod_get(&pdev->dev, "bt-host-wake");
> + BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-host-wake returned %p",
> + p_bcm_device->bt_wake_gpio);
> + if (IS_ERR(p_bcm_device->bt_wake_gpio)) {
> + ret = PTR_ERR(p_bcm_device->bt_wake_gpio);
> + if (ret != -ENOENT) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - bt_wake GPIO: %d\n", ret);
> + }
> + p_bcm_device->bt_wake_gpio = NULL;
> + } else {
> + /* configure bt_wake as input */
> + ret = gpiod_direction_input(p_bcm_device->bt_wake_gpio);
> + if (ret< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe s bt_wake GPIO: %d\n", ret);
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + } else {
> + p_bcm_device->bt_wake_active_low = gpiod_is_active_low
> + (p_bcm_device->bt_wake_gpio);
> + BT_DBG("bcm_bt_uart_probe -bt_wake a-low is %d(cans%d)",
> + p_bcm_device->bt_wake_active_low,
> + gpiod_cansleep(p_bcm_device->bt_wake_gpio));
> + p_bcm_device->bt_wake_irq = gpiod_to_irq
> + (p_bcm_device->bt_wake_gpio);
> + if (p_bcm_device->bt_wake_irq< 0) {
> + dev_err(&pdev->dev,
> + "bcm_bt_uart_probe - HOST_WAKE IRQ: %d\n", ret);
> + } else {
> + unsigned long intflags = IRQF_TRIGGER_RISING;
> +
> + if (p_bcm_device->bt_wake_active_low)
> + intflags = IRQF_TRIGGER_FALLING;
> +
> + ret = request_irq(p_bcm_device->bt_wake_irq,
> + bcm_bt_uart_isr,
> + intflags, "bt_host_wake",
> + p_bcm_device);
> + if (ret< 0) {
> + dev_err(&pdev->dev, "bcm_bt_uart_probe - failed to conf IRQ %d: %d",
> + p_bcm_device->bt_wake_irq, ret);
> + } else {
> + BT_DBG("bcm_bt_uart_probe - IRQ %d",
> + p_bcm_device->bt_wake_irq);
> + }
> + }
> + }
> + }
> +
> + p_bcm_device->configure_sleep = 0;
> + if (!of_property_read_u32(np, "configure-sleep",
> + &p_bcm_device->configure_sleep)) {
> + BT_DBG("configure-sleep read as %d",
> + p_bcm_device->configure_sleep);
> + }
> + p_bcm_device->manual_fc = 0;
> + if (!of_property_read_u32(np, "manual-fc",
> + &p_bcm_device->manual_fc)) {
> + BT_DBG("manual-fc read as %d",
> + p_bcm_device->manual_fc);
> + }
> + p_bcm_device->baud_rate_before_config_download = 3000000;
> + if (!of_property_read_u32(
> + np, "baud-rate-before-config-download",
> + &p_bcm_device->baud_rate_before_config_download)) {
> + BT_DBG("baud-rate-before-config-download read as %d",
> + p_bcm_device->baud_rate_before_config_download);
> + }
> + p_bcm_device->configure_audio = 0;
> + if (!of_property_read_u32(np, "configure-audio",
> + &p_bcm_device->configure_audio)) {
> + BT_DBG("configure-audio read as %d",
> + p_bcm_device->configure_audio);
> + }
> + if (p_bcm_device->configure_audio) {
> + /* Defaults for audio */
> + p_bcm_device->PCMClockMode = 0;
> + p_bcm_device->PCMFillMethod = 2;
> + p_bcm_device->PCMFillNum = 0;
> + p_bcm_device->PCMFillValue = 3;
> + p_bcm_device->PCMInCallBitclock = 0;
> + p_bcm_device->PCMLSBFirst = 0;
> + p_bcm_device->PCMRightJustify = 0;
> + p_bcm_device->PCMRouting = 0;
> + p_bcm_device->PCMShortFrameSync = 0;
> + p_bcm_device->PCMSyncMode = 0;
> +
> + if (!of_property_read_u32(np, "PCMClockMode",
> + &p_bcm_device->PCMClockMode))
> + BT_DBG("PCMClockMode read as %d",
> + p_bcm_device->PCMClockMode);
> + if (!of_property_read_u32(np, "PCMFillMethod",
> + &p_bcm_device->PCMFillMethod))
> + BT_DBG("PCMFillMethod readas %d",
> + p_bcm_device->PCMFillMethod);
> + if (!of_property_read_u32(np, "PCMFillNum",
> + &p_bcm_device->PCMFillNum))
> + BT_DBG("PCMFillNum read as %d",
> + p_bcm_device->PCMFillNum);
> + if (!of_property_read_u32(np, "PCMFillValue",
> + &p_bcm_device->PCMFillValue))
> + BT_DBG("PCMFillValue read as %d",
> + p_bcm_device->PCMFillValue);
> + if (!of_property_read_u32(np, "PCMInCallBitclock",
> + &p_bcm_device->PCMInCallBitclock))
> + BT_DBG("PCMInCallBitclock read as %d",
> + p_bcm_device->PCMInCallBitclock);
> + if (!of_property_read_u32(np, "PCMLSBFirst",
> + &p_bcm_device->PCMLSBFirst))
> + BT_DBG("PCMLSBFirst read as %d",
> + p_bcm_device->PCMLSBFirst);
> + if (!of_property_read_u32(np, "PCMRightJustify",
> + &p_bcm_device->PCMRightJustify))
> + BT_DBG("PCMRightJustify read as %d",
> + p_bcm_device->PCMRightJustify);
> + if (!of_property_read_u32(np, "PCMRouting",
> + &p_bcm_device->PCMRouting))
> + BT_DBG("PCMRouting read as %d",
> + p_bcm_device->PCMRouting);
> + if (!of_property_read_u32(np, "PCMShortFrameSync",
> + &p_bcm_device->PCMShortFrameSync))
> + BT_DBG("PCMShortFrameSync read as %d",
> + p_bcm_device->PCMShortFrameSync);
> + if (!of_property_read_u32(np, "PCMSyncMode",
> + &p_bcm_device->PCMSyncMode))
> + BT_DBG("PCMSyncMode read as %d",
> + p_bcm_device->PCMSyncMode);
> + }
> +
> + if (!of_property_read_string(np, "tty",&tty_name)) {
> + strcpy(p_bcm_device->tty_name, tty_name);
> + BT_DBG("tty name read as %s", p_bcm_device->tty_name);
> + }
> +
> + BT_DBG("idle_timeout set as %d", idle_timeout);
> +
> + ret = 0; /* If we made it here, we're fine */
> +
> + /* Place this instance on the device list */
> + spin_lock(&device_list_lock);
> + list_add_tail(&p_bcm_device->list,&device_list);
> + spin_unlock(&device_list_lock);
> +
> +end:
> + if (ret) {
> + if (p_bcm_device->reg_on_gpio) {
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + }
> + if (p_bcm_device->bt_wake_gpio) {
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + }
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + }
> + }
> +
> + BT_DBG("bcm_bt_uart_probe with the result %d", ret);
> + return ret;
> +}
> +
> +/*
> + * Device instance removal
> + */
> +static int bcm_bt_uart_remove(struct platform_device *pdev)
> +{
> + struct bcm_device *p_bcm_device = platform_get_drvdata(pdev);
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_remove - logic error, no probe?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_remove %p context", p_bcm_device);
> +
> + spin_lock(&device_list_lock);
> + list_del(&p_bcm_device->list);
> + spin_unlock(&device_list_lock);
> +
> + BT_DBG("bcm_bt_uart_remove - freeing interrupt %d",
> + p_bcm_device->bt_wake_irq);
> + free_irq(p_bcm_device->bt_wake_irq, p_bcm_device);
> +
> + if (p_bcm_device->reg_on_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing reg_on_gpio");
> + gpiod_put(p_bcm_device->reg_on_gpio);
> + p_bcm_device->reg_on_gpio = NULL;
> + }
> +
> + if (p_bcm_device->dev_wake_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing dev_wake_gpio");
> + gpiod_put(p_bcm_device->dev_wake_gpio);
> + p_bcm_device->dev_wake_gpio = NULL;
> + }
> +
> + if (p_bcm_device->bt_wake_gpio) {
> + BT_DBG("bcm_bt_uart_remove - releasing bt_wake_gpio");
> + gpiod_put(p_bcm_device->bt_wake_gpio);
> + p_bcm_device->bt_wake_gpio = NULL;
> + }
> +
> + BT_DBG("bcm_bt_uart_remove %p done", p_bcm_device);
> + return 0;
> +}
> +
> +/*
> + * Platform resume callback
> + */
> +static int bcm_bt_uart_resume(struct device *pdev)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device = platform_get_drvdata(
> + to_platform_device(pdev));
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_resume - logic error, no device?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_resume %p", p_bcm_device);
> +
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + if (p_bcm_device->dev_wake_gpio) {
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
> + BT_DBG("bcm_bt_uart_resume: %d written, delaying 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> +
> + /* Let the protocol know the platform is resuming */
> + if (p_bcm_device->protocol_callbacks.p_resume)
> + p_bcm_device->protocol_callbacks.p_resume(
> + p_bcm_device->protocol_callbacks.context);
> +
> + return 0;
> +}
> +
> +/*
> + * Platform suspend callback
> + */
> +static int bcm_bt_uart_suspend(struct device *pdev)
> +{
> + int resume_flag;
> + struct bcm_device *p_bcm_device = platform_get_drvdata(
> + to_platform_device(pdev));
> +
> + if (p_bcm_device == NULL) {
> + BT_DBG("bcm_bt_uart_suspend - logic error, no device?!");
> + return 0;
> + }
> +
> + BT_DBG("bcm_bt_uart_suspend %p", p_bcm_device);
> +
> + /* Let the protocol know the platform is suspending */
> + if (p_bcm_device->protocol_callbacks.p_suspend)
> + p_bcm_device->protocol_callbacks.p_suspend(
> + p_bcm_device->protocol_callbacks.context);
> +
> + /* Suspend the device */
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio, !resume_flag);

At first I though there was a mistake being made here with resume_flag
being set on wake gpio as it looked exactly the same as in resume()
callback. I overlooked the second inversion in gpiod_set_value(). I
think it is more clear to rename the variable to 'gpio_value' and say:

gpio_value = !!p_bcm_device->dev_wake_active_low;
gpiod_set_value(p_bcm_device->dev_wake_gpio, gpio_value);


> + BT_DBG("bcm_bt_uart_suspend: %d written, delaying 15 ms",
> + !resume_flag);
> + mdelay(15);
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Entry point for calls from the protocol
> + */
> +int btbcm_uart_control(int action, void *device_context,
> + void *p_data, unsigned long *p_size)

By using multiplexing function and consequently using void pointer
parameters you loose type checking. So are the benefits justifying this
construct.

> +{
> + struct btbcm_uart_callbacks *pc;
> + struct btbcm_uart_parameters *pp = p_data; /* for pars action only */
> + int ret = 0;
> + int resume_flag, poweron_flag;
> + struct bcm_device *p_bcm_device = device_context;
> + struct list_head *ptr;
> + bool is_found = false;
> +
> + /* Special processing for the callback configuration */
> + if (action == BTBCM_UART_ACTION_CONFIGURE_CALLBACKS) {
> + pc = p_data;
> +
> + BT_DBG("btbcm_uart_control - configure callbacks");
> + if (p_data == NULL || *p_size != sizeof(struct
> + btbcm_uart_callbacks) || (pc->interface_version !=
> + BTBCM_UART_INTERFACE_VERSION)) {
> + BT_DBG("btbcm_uart_control - callbacks mismatch!");
> + return -E2BIG;
> + }
> +
> + BT_DBG("btbcm_uart_control - configure callbacks for %s(%p)",
> + pc->name, pc->context);
> + if (p_bcm_device == NULL) {
> + spin_lock(&device_list_lock);
> + list_for_each(ptr,&device_list) {
> + p_bcm_device = list_entry(ptr, struct
> + bcm_device, list);
> + if (!strcmp(p_bcm_device->tty_name, pc->name)) {
> + is_found = true;
> + break;
> + }
> + }
> +
> + spin_unlock(&device_list_lock);
> + if (!is_found) {
> + BT_DBG("btbcm_uart_control - no device!");
> + return -ENOENT;
> + }
> + }
> +
> + p_bcm_device->protocol_callbacks = *pc;
> + memcpy(p_data,&p_bcm_device, sizeof(p_bcm_device));
> + *p_size = sizeof(p_bcm_device);
> + return ret;
> + }
> +
> + /* All other requests must have the right context */
> + if (p_bcm_device == NULL) {
> + BT_DBG("btbcm_uart_control - failing, no device");
> + return -ENOENT;
> + }
> +
> + switch (action) {
> + case BTBCM_UART_ACTION_POWER_ON:
> + BT_DBG("btbcm_uart_control %p - power on", device_context);
> + if (p_bcm_device->reg_on_gpio) {
> + poweron_flag = !p_bcm_device->reg_on_active_low;
> + gpiod_set_value(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + BT_DBG("btbcm_uart_control - pwron %d, delay 15 ms",
> + poweron_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_POWER_OFF:
> + BT_DBG("btbcm_uart_control %p - power off", device_context);
> + if (p_bcm_device->reg_on_gpio) {
> + poweron_flag = p_bcm_device->reg_on_active_low;
> + gpiod_set_value(p_bcm_device->reg_on_gpio,
> + poweron_flag);
> + BT_DBG("btbcm_uart_control - pwroff %d, delay 15 ms",
> + poweron_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_RESUME:
> + BT_DBG("btbcm_uart_control %p - resume", device_context);
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio,
> + resume_flag);
> + BT_DBG("btbcm_uart_control - resume %d, delay 15 ms",
> + resume_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_SUSPEND:
> + BT_DBG("btbcm_uart_control %p - suspend", device_context);
> + if (p_bcm_device->dev_wake_gpio) {
> + resume_flag = !p_bcm_device->dev_wake_active_low;
> + gpiod_set_value(p_bcm_device->dev_wake_gpio,
> + !resume_flag);
> + BT_DBG("btbcm_uart_control - suspend %d, delay 15ms",
> + !resume_flag);
> + mdelay(15);
> + }
> + break;
> +
> + case BTBCM_UART_ACTION_GET_PARAMETERS:
> + BT_DBG("btbcm_uart_control %p - get pars", device_context);
> + if ((p_data == NULL) ||
> + (*p_size< sizeof(struct btbcm_uart_parameters))) {
> + BT_DBG("btbcm_uart_control - failing, wrong par size");
> + return -E2BIG;
> + }
> +
> + memset(pp, 0, sizeof(struct btbcm_uart_parameters));
> + pp->interface_version = BTBCM_UART_INTERFACE_VERSION;
> + pp->configure_sleep = p_bcm_device->configure_sleep;
> + pp->manual_fc = p_bcm_device->manual_fc;
> + pp->dev_wake_active_low = p_bcm_device->dev_wake_active_low;
> + pp->bt_wake_active_low = p_bcm_device->bt_wake_active_low;
> + pp->idle_timeout_in_secs = idle_timeout;
> + pp->baud_rate_before_config_download =
> + p_bcm_device->baud_rate_before_config_download;
> + pp->configure_audio = p_bcm_device->configure_audio;
> + pp->PCMClockMode = p_bcm_device->PCMClockMode;
> + pp->PCMFillMethod = p_bcm_device->PCMFillMethod;
> + pp->PCMFillNum = p_bcm_device->PCMFillNum;
> + pp->PCMFillValue = p_bcm_device->PCMFillValue;
> + pp->PCMInCallBitclock = p_bcm_device->PCMInCallBitclock;
> + pp->PCMLSBFirst = p_bcm_device->PCMLSBFirst;
> + pp->PCMRightJustify = p_bcm_device->PCMRightJustify;
> + pp->PCMRouting = p_bcm_device->PCMRouting;
> + pp->PCMShortFrameSync = p_bcm_device->PCMShortFrameSync;
> + pp->PCMSyncMode = p_bcm_device->PCMSyncMode;
> + *p_size = sizeof(struct btbcm_uart_parameters);
> + break;
> +
> + default:
> + BT_DBG("btbcm_uart_control %p unknown act %d",
> + device_context, action);
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(btbcm_uart_control);
> +
> +/* Platform susp and resume callbacks */
> +static SIMPLE_DEV_PM_OPS(bcm_bt_uart_pm_ops,
> + bcm_bt_uart_suspend, bcm_bt_uart_resume);
> +
> +/* Driver match table */
> +static const struct of_device_id bcm_bt_uart_table[] = {
> + { .compatible = "brcm,brcm-bt-uart" },
> + {}
> +};
> +
> +/* Driver configuration */
> +static struct platform_driver bcm_bt_uart_driver = {
> + .probe = bcm_bt_uart_probe,
> + .remove = bcm_bt_uart_remove,
> + .driver = {
> + .name = "brcm_bt_uart",
> + .of_match_table = of_match_ptr(bcm_bt_uart_table),
> + .owner = THIS_MODULE,
> + .pm =&bcm_bt_uart_pm_ops,
> + },
> +};
> +
> +module_platform_driver(bcm_bt_uart_driver);
> +
> +MODULE_AUTHOR("Ilya Faenson");
> +MODULE_DESCRIPTION("Broadcom Bluetooth UART Driver");
> +MODULE_LICENSE("Dual BSD/GPL");
> +
> diff --git a/drivers/bluetooth/btbcm_uart.h b/drivers/bluetooth/btbcm_uart.h
> new file mode 100644
> index 0000000..3b02a4e
> --- /dev/null
> +++ b/drivers/bluetooth/btbcm_uart.h
> @@ -0,0 +1,116 @@
> +/*
> + * Bluetooth BCM UART Driver Header
> + *
> + * Copyright (c) 2015 Broadcom Corporation
> + *
> + * This file is provided under a dual BSD/GPLv2 license. When using or
> + * redistributing this file, you may do so under either license.
> + *
> + * GPL LICENSE SUMMARY
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + * 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.
> + *
> + * BSD LICENSE SUMMARY
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in
> + * the documentation and/or other materials provided with the
> + * distribution.
> + * Neither the name of Intel Corporation nor the names of its

Guess you copied this somewhere. Should it say Broadcom Corporation
instead? Actually there is no such thing as a dual BSD/GPLv2 license.
The MODULE_LICENSE value "Dual BSD/GPL" only indicates that the license
for this file is compatible with the terms of both BSD and GPLv2. That
said we have internal guideline on license statements in source files. I
will let you know where to find those privately.

Regards,
Arend

> + * contributors may be used to endorse or promote products derived
> + * from this software without specific prior written permission.
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
> + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
> + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
> + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
> + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#ifndef BTBCM_UART_H
> +#define BTBCM_UART_H
> +
> +/* Change the version if you change anything in this header */
> +#define BTBCM_UART_INTERFACE_VERSION 1
> +/* Callbacks from the driver into the protocol */
> +typedef void (*p_suspend_callback)(void *context);
> +typedef void (*p_resume_callback)(void *context);
> +typedef void (*p_wakeup_callback)(void *context);
> +struct btbcm_uart_callbacks {
> + int interface_version; /* interface # compiled against */
> + void *context; /* protocol instance context */
> + char name[64]; /* protocol tty device, for example, ttyS0 */
> +
> + /* client callbacks */
> + p_suspend_callback p_suspend;
> + p_resume_callback p_resume;
> + p_wakeup_callback p_wakeup;
> +};
> +
> +/* Driver parameters retrieved from the DT or ACPI */
> +struct btbcm_uart_parameters {
> + int interface_version; /* interface # compiled against */
> +
> + /* Parameters themselves */
> + int configure_sleep;
> + int manual_fc;
> + int dev_wake_active_low;
> + int bt_wake_active_low;
> + int idle_timeout_in_secs;
> + int baud_rate_before_config_download;
> + int configure_audio;
> + int PCMClockMode;
> + int PCMFillMethod;
> + int PCMFillNum;
> + int PCMFillValue;
> + int PCMInCallBitclock;
> + int PCMLSBFirst;
> + int PCMRightJustify;
> + int PCMRouting;
> + int PCMShortFrameSync;
> + int PCMSyncMode;
> +};
> +
> +/*
> + * Actions on the BTBCM_UART driver
> + */
> +
> +/* Configure protocol callbacks */
> +#define BTBCM_UART_ACTION_CONFIGURE_CALLBACKS 0
> +
> +/* Retrieve BT device parameters */
> +#define BTBCM_UART_ACTION_GET_PARAMETERS 1
> +
> +/* Resume the BT device via GPIO */
> +#define BTBCM_UART_ACTION_RESUME 2
> +
> +/* Suspend the BT device via GPIO */
> +#define BTBCM_UART_ACTION_SUSPEND 3
> +
> +/* Power the BT device off via GPIO */
> +#define BTBCM_UART_ACTION_POWER_OFF 4
> +
> +/* Power the BT device on via GPIO */
> +#define BTBCM_UART_ACTION_POWER_ON 5
> +
> +/* Execute an action on the BT device */
> +extern int btbcm_uart_control(int action, void *device_context,
> + void *p_data, unsigned long *p_size);
> +
> +#endif
> +


2015-05-19 09:22:53

by Arend van Spriel

[permalink] [raw]
Subject: Re: [RFC v3 1/4] Broadcom Bluetooth UART Device Tree bindings

On 05/13/15 23:50, Ilya Faenson wrote:
> Device Tree bindings to configure the Broadcom Bluetooth UART device.

As Marcel indicated this patch needs to be reviewed by devicetree folks
as well so add following to the commit message:

Cc: [email protected]
Cc: [email protected]
> Signed-off-by: Ilya Faenson<[email protected]>
> ---
> .../devicetree/bindings/net/bluetooth/btbcm.txt | 54 ++++++++++++++++++++++
> 1 file changed, 54 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
>
> diff --git a/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt b/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
> new file mode 100644
> index 0000000..cc9f225
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
> @@ -0,0 +1,54 @@
> +btbcm
> +------
> +
> +Required properties:
> +
> + - compatible : must be "brcm,brcm-bt-uart".

The devicetree folks will probably want to see a specific device id
here. Not sure where there are means to obtain that from the device
itself, which could be an argument not to use it.

> + - tty : tty device connected to this Bluetooth device.
> +
> +Optional properties:
> +
> + - bt-host-wake-gpios : bt-host-wake input GPIO to be used as an interrupt.
> +
> + - bt-wake-gpios : bt-wake output GPIO to be used to suspend / resume device.
> +
> + - reg-on-gpios : reg-on output GPIO to be used to power device on/off.

Maybe use bt-reg-on-gpios for sake of consistency with other gpio
properties.

> + - baud-rate-before-config-download : initial Bluetooth device baud rate.
> + Default: 3000000.
> +
> + - manual-fc : flow control UART in suspend / resume scenarios.
> + Default: 0.
> +
> + - configure-sleep : configure suspend / resume flag.
> + Default: 0.
> +
> + - configure-audio : configure platform PCM SCO flag.
> + Default: 0.
> +
> + - PCM* : SCO PCM platform parameters. Work with Broadcom on setting.

What does "Work with Broadcom on setting" mean here?

> + Defaults: see the example below.

Devicetree properties that are for a specific device should be have
vendor prefix. I can imagine some of the properties are generic to any
bt device and/or bt-behind-uart.

> +Example:
> +
> + bcm4354_bt_uart: bcm4354-bt-uart {
> + compatible = "bcm-bt-uart,bcm4354-bt-uart";

This compatible string here should match the described compatible
property above.

> + bt-wake-gpios =<&gpio4 30 GPIO_ACTIVE_HIGH>;
> + bt-host-wake-gpios =<&gpio4 31 GPIO_ACTIVE_HIGH>;
> + tty = "ttyS0";
> + baud-rate-before-config-download =<3000000>;
> + configure-sleep =<1>;
> + configure-audio =<1>;

Are these configure flags boolean. If so you can drop the assignment.
The presence of the property itself can be used.

Regards,
Arend

> + PCMClockMode =<0>;
> + PCMFillMethod =<2>;
> + PCMFillNum =<0>;
> + PCMFillValue =<3>;
> + PCMInCallBitclock =<0>;
> + PCMLSBFirst =<0>;
> + PCMRightJustify =<0>;
> + PCMRouting =<0>;
> + PCMShortFrameSync =<0>;
> + PCMSyncMode =<0>;
> + };
> +

2015-05-19 08:19:56

by Arend van Spriel

[permalink] [raw]
Subject: Re: [RFC v3 0/4] Broadcom Bluetooth UART device driver

On 05/13/15 23:50, Ilya Faenson wrote:
> The next version of the driver changed as per Marcel's comments.

This is just a cover letter and as such not stored in the git
repository, but it is stored in the mailing list archives so I would
recommend giving a short description here about what this patch series
provides followed by a change log so you can keep the description and
indicate in the change log what was done in the next version.

Regards,
Arend

> Ilya Faenson (4):
> Broadcom Bluetooth UART Device Tree bindings
> Broadcom Bluetooth UART Platform Driver
> Broadcom Bluetooth protocol UART support
> BlueZ Broadcom UART Protocol
>
> .../devicetree/bindings/net/bluetooth/btbcm.txt | 54 ++
> drivers/bluetooth/Kconfig | 9 +
> drivers/bluetooth/Makefile | 1 +
> drivers/bluetooth/btbcm.c | 155 ++++-
> drivers/bluetooth/btbcm.h | 13 +
> drivers/bluetooth/btbcm_uart.c | 705 +++++++++++++++++++++
> drivers/bluetooth/btbcm_uart.h | 116 ++++
> drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++-
> 8 files changed, 1561 insertions(+), 20 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
> create mode 100644 drivers/bluetooth/btbcm_uart.c
> create mode 100644 drivers/bluetooth/btbcm_uart.h
>

2015-05-13 21:50:22

by Ilya Faenson

[permalink] [raw]
Subject: [RFC v3 3/4] Broadcom Bluetooth protocol UART support

Signed-off-by: Ilya Faenson <[email protected]>
---
drivers/bluetooth/btbcm.c | 155 ++++++++++++++++++++++++++++++++++++++++++++--
drivers/bluetooth/btbcm.h | 13 ++++
2 files changed, 163 insertions(+), 5 deletions(-)

diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c
index 4bba866..8b5530d 100644
--- a/drivers/bluetooth/btbcm.c
+++ b/drivers/bluetooth/btbcm.c
@@ -3,6 +3,7 @@
* Bluetooth support for Broadcom devices
*
* Copyright (C) 2015 Intel Corporation
+ * Copyright (C) 2015 Broadcom Corporation
*
*
* This program is free software; you can redistribute it and/or modify
@@ -23,14 +24,16 @@

#include <linux/module.h>
#include <linux/firmware.h>
+#include <linux/tty.h>
#include <asm/unaligned.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

+#include "hci_uart.h"
#include "btbcm.h"

-#define VERSION "0.1"
+#define VERSION "0.2"

#define BDADDR_BCM20702A0 (&(bdaddr_t) {{0x00, 0xa0, 0x02, 0x70, 0x20, 0x00}})

@@ -246,8 +249,10 @@ static struct sk_buff *btbcm_read_usb_product(struct hci_dev *hdev)
static const struct {
u16 subver;
const char *name;
+ u32 baud_rate; /* operational baud rate */
} bcm_uart_subver_table[] = {
- { 0x410e, "BCM43341B0" }, /* 002.001.014 */
+ { 0x410e, "BCM43341B0", 3000000}, /* 002.001.014 */
+ { 0x610c, "BCM4354_003.001.012.0306.0659", 3000000}, /* 003.001.012 */
{ }
};

@@ -268,6 +273,127 @@ static const struct {
{ }
};

+/*
+ * Set the UART into the defaults
+ */
+int btbcm_init_uart(struct hci_uart *hu)
+{
+ struct tty_struct *tty = hu->tty;
+ struct ktermios ktermios;
+ int err, speed;
+
+ /* Flush the line discipline buffers and the TTY buffers */
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+ tty_driver_flush_buffer(tty);
+
+
+ /* Init UART to default settings */
+ ktermios = tty->termios;
+ ktermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ ktermios.c_oflag &= ~OPOST;
+ ktermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ ktermios.c_cflag &= ~(CSIZE | PARENB | CBAUD);
+ ktermios.c_cflag |= CS8;
+ ktermios.c_cflag |= CRTSCTS;
+ ktermios.c_cflag |= B115200;
+ ktermios.c_ispeed = 115200;
+ ktermios.c_ospeed = 115200;
+ err = tty_set_termios(tty, &ktermios);
+ if (err) {
+ BT_DBG("init_uart set_termios failure %d", err);
+ return err;
+ }
+
+ speed = tty_get_baud_rate(tty);
+
+ BT_DBG("init_uart set_termios completed, spd %d", speed);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(btbcm_init_uart);
+
+/*
+ * Set the baud rate on the UART and the device
+ */
+int btbcm_set_baud_rate(struct hci_uart *hu, int baud_rate)
+{
+ struct tty_struct *tty = hu->tty;
+ struct ktermios ktermios;
+ int status, speed, cflag;
+ struct sk_buff *skb;
+ u8 enable = 0x01;
+ u8 baud_rate_vsc_pars[] = {0, 0, 0, 0x10, 0x0e, 0};
+
+ /* If the baud rate is higher than 3000000, change the clock */
+ if (baud_rate > 3000000) {
+ skb = __hci_cmd_sync(hu->hdev, 0xfc45, 1, &enable,
+ HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ return status;
+ }
+
+ kfree_skb(skb);
+ BT_DBG("set_baud_rate write UART 48 MHz VSC succeeded");
+ }
+
+ /* Now let the device know about the rate change */
+ put_unaligned_le32((u32)baud_rate, &baud_rate_vsc_pars[2]);
+ skb = __hci_cmd_sync(hu->hdev, 0xfc18, sizeof(baud_rate_vsc_pars),
+ baud_rate_vsc_pars, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("set_baud_rate VSC failed (%d)", status);
+ return status;
+ }
+
+ kfree_skb(skb);
+ BT_DBG("set_baud_rate VSC succeeded");
+
+ /* Set UART into this rate as well */
+ ktermios = tty->termios;
+ BT_DBG("set_baud_rate start flags c_o %x c_l %x c_c %x spd %d/%d",
+ ktermios.c_oflag, ktermios.c_lflag, ktermios.c_cflag,
+ ktermios.c_ispeed, ktermios.c_ospeed);
+ switch (baud_rate) {
+ case 115200:
+ cflag |= B115200; break;
+ case 921600:
+ cflag |= B921600; break;
+ case 3000000:
+ cflag |= B3000000; break;
+ case 3500000:
+ cflag |= B3500000; break;
+ case 4000000:
+ cflag |= B4000000; break;
+ default:
+ BT_DBG("set_baud_rate unknown rate %d", baud_rate);
+ return -EINVAL;
+ }
+
+ ktermios.c_cflag &= ~CBAUD;
+ ktermios.c_cflag |= cflag;
+ ktermios.c_ispeed = baud_rate;
+ ktermios.c_ospeed = baud_rate;
+ status = tty_set_termios(tty, &ktermios);
+ if (status) {
+ BT_DBG("set_baud_rate set_termios failure %d", status);
+ return status;
+ }
+
+ speed = tty_get_baud_rate(tty);
+ BT_DBG("set_baud_rate set_termios completed, spd %d", speed);
+ ktermios = tty->termios;
+ BT_DBG("set_baud_rate flags c_o %x c_l %x c_c %x spd %d/%d",
+ ktermios.c_oflag, ktermios.c_lflag, ktermios.c_cflag,
+ ktermios.c_ispeed, ktermios.c_ospeed);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(btbcm_set_baud_rate);
+
int btbcm_setup_patchram(struct hci_dev *hdev)
{
char fw_name[64];
@@ -275,7 +401,8 @@ int btbcm_setup_patchram(struct hci_dev *hdev)
const char *hw_name = NULL;
struct sk_buff *skb;
struct hci_rp_read_local_version *ver;
- int i, err;
+ int i, err, is_uart = false;
+ struct hci_uart *hu = hci_get_drvdata(hdev);

/* Reset */
err = btbcm_reset(hdev);
@@ -297,14 +424,18 @@ int btbcm_setup_patchram(struct hci_dev *hdev)
if (IS_ERR(skb))
return PTR_ERR(skb);

- BT_INFO("%s: BCM: chip id %u", hdev->name, skb->data[1]);
+ BT_INFO("%s: BCM: chip id %u, rev 0x%x subver 0x%x",
+ hdev->name, skb->data[1], rev, subver);
kfree_skb(skb);

switch ((rev & 0xf000) >> 12) {
case 0:
+ case 1:
for (i = 0; bcm_uart_subver_table[i].name; i++) {
if (subver == bcm_uart_subver_table[i].subver) {
hw_name = bcm_uart_subver_table[i].name;
+ BT_INFO("UART firmware found: %s", hw_name);
+ is_uart = true;
break;
}
}
@@ -312,7 +443,7 @@ int btbcm_setup_patchram(struct hci_dev *hdev)
snprintf(fw_name, sizeof(fw_name), "brcm/%s.hcd",
hw_name ? : "BCM");
break;
- case 1:
+
case 2:
/* Read USB Product Info */
skb = btbcm_read_usb_product(hdev);
@@ -345,11 +476,25 @@ int btbcm_setup_patchram(struct hci_dev *hdev)
if (err == -ENOENT)
return 0;

+ /* Once the patch is downloaded, the device is back at default rate */
+ if (is_uart) {
+ err = btbcm_init_uart(hu);
+ if (err)
+ return 0;
+ }
+
/* Reset */
err = btbcm_reset(hdev);
if (err)
return err;

+ if (is_uart) {
+ err = btbcm_set_baud_rate(hu,
+ bcm_uart_subver_table[i].baud_rate);
+ if (err)
+ return 0;
+ }
+
/* Read Local Version Info */
skb = btbcm_read_local_version(hdev);
if (IS_ERR(skb))
diff --git a/drivers/bluetooth/btbcm.h b/drivers/bluetooth/btbcm.h
index eb6ab5f..77fa60a 100644
--- a/drivers/bluetooth/btbcm.h
+++ b/drivers/bluetooth/btbcm.h
@@ -3,6 +3,7 @@
* Bluetooth support for Broadcom devices
*
* Copyright (C) 2015 Intel Corporation
+ * Copyright (C) 2015 Broadcom Corporation
*
*
* This program is free software; you can redistribute it and/or modify
@@ -30,6 +31,8 @@ int btbcm_patchram(struct hci_dev *hdev, const char *firmware);
int btbcm_setup_patchram(struct hci_dev *hdev);
int btbcm_setup_apple(struct hci_dev *hdev);

+int btbcm_init_uart(struct hci_uart *hu);
+int btbcm_set_baud_rate(struct hci_uart *hu, int baud_rate);
#else

static inline int btbcm_check_bdaddr(struct hci_dev *hdev)
@@ -57,4 +60,14 @@ static inline int btbcm_setup_apple(struct hci_dev *hdev)
return 0;
}

+static inline int btbcm_init_uart(void *hu)
+{
+ return 0;
+}
+
+static inline int btbcm_set_baud_rate(void *hu, int baud_rate);
+{
+ return 0;
+}
+
#endif
--
1.9.1


2015-05-13 21:50:21

by Ilya Faenson

[permalink] [raw]
Subject: [RFC v3 2/4] Broadcom Bluetooth UART Platform Driver

Introduce the device tree enumerated Broadcom Bluetooth UART driver.

Signed-off-by: Ilya Faenson <[email protected]>
---
drivers/bluetooth/Kconfig | 9 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btbcm_uart.c | 705 +++++++++++++++++++++++++++++++++++++++++
drivers/bluetooth/btbcm_uart.h | 116 +++++++
4 files changed, 831 insertions(+)
create mode 100644 drivers/bluetooth/btbcm_uart.c
create mode 100644 drivers/bluetooth/btbcm_uart.h

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index ed5c273..7127ada 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -128,6 +128,7 @@ config BT_HCIUART_BCM
bool "Broadcom protocol support"
depends on BT_HCIUART
select BT_HCIUART_H4
+ select BT_UART_BCM
select BT_BCM
help
The Broadcom protocol support enables Bluetooth HCI over serial
@@ -135,6 +136,14 @@ config BT_HCIUART_BCM

Say Y here to compile support for Broadcom protocol.

+config BT_UART_BCM
+ tristate "Broadcom BT UART driver"
+ depends on BT_HCIUART_H4 && TTY
+ help
+ This driver supports the HCI_UART_BT protocol.
+
+ It manages Bluetooth UART device properties and GPIOs.
+
config BT_HCIBCM203X
tristate "HCI BCM203x USB driver"
depends on USB
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index dd0d9c4..0e5fd66 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BT_MRVL) += btmrvl.o
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
obj-$(CONFIG_BT_WILINK) += btwilink.o
obj-$(CONFIG_BT_BCM) += btbcm.o
+obj-$(CONFIG_BT_UART_BCM) += btbcm_uart.o

btmrvl-y := btmrvl_main.o
btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
diff --git a/drivers/bluetooth/btbcm_uart.c b/drivers/bluetooth/btbcm_uart.c
new file mode 100644
index 0000000..2afaed1
--- /dev/null
+++ b/drivers/bluetooth/btbcm_uart.c
@@ -0,0 +1,705 @@
+/*
+ * Bluetooth BCM UART Driver
+ *
+ * Copyright (c) 2015 Broadcom Corporation
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 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.
+ *
+ * BSD LICENSE SUMMARY
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/poll.h>
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/signal.h>
+#include <linux/ioctl.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+
+#include "btbcm_uart.h"
+
+static int idle_timeout = 5;
+module_param(idle_timeout, int, 0);
+MODULE_PARM_DESC(idle_timeout, "Bluetooth idle timeout in seconds");
+
+/* Device context */
+struct bcm_device {
+ struct list_head list;
+
+ struct platform_device *pdev;
+ struct gpio_desc *bt_wake_gpio;
+ struct gpio_desc *dev_wake_gpio;
+ struct gpio_desc *reg_on_gpio;
+ int bt_wake_irq;
+ int dev_wake_active_low;
+ int reg_on_active_low;
+ int bt_wake_active_low;
+ u32 configure_sleep;
+ u32 manual_fc;
+ u32 baud_rate_before_config_download;
+ u32 configure_audio;
+ u32 PCMClockMode;
+ u32 PCMFillMethod;
+ u32 PCMFillNum;
+ u32 PCMFillValue;
+ u32 PCMInCallBitclock;
+ u32 PCMLSBFirst;
+ u32 PCMRightJustify;
+ u32 PCMRouting;
+ u32 PCMShortFrameSync;
+ u32 PCMSyncMode;
+
+ char tty_name[64];
+
+ struct btbcm_uart_callbacks protocol_callbacks;
+ struct work_struct wakeup_work;
+};
+
+/* List of BCM BT UART devices */
+static DEFINE_SPINLOCK(device_list_lock);
+static LIST_HEAD(device_list);
+
+/*
+ * Calling the BCM protocol at lower execution priority
+ */
+static void bcm_bt_wakeup_task(struct work_struct *ws)
+{
+ int resume_flag;
+ struct bcm_device *p_bcm_device =
+ container_of(ws, struct bcm_device, wakeup_work);
+
+ if (!p_bcm_device) {
+ BT_DBG("bcm_bt_wakeup_task - failing, no device");
+ return;
+ }
+
+ /* Make sure the device is resumed */
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ if (p_bcm_device->dev_wake_gpio) {
+ gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
+ BT_DBG("bcm_bt_wakeup_task - resume %d written, delaying 15 ms",
+ resume_flag);
+ mdelay(15);
+ }
+
+ /* Let the protocol know it's time to wake up */
+ if (p_bcm_device->protocol_callbacks.p_wakeup)
+ p_bcm_device->protocol_callbacks.p_wakeup(
+ p_bcm_device->protocol_callbacks.context);
+}
+
+/*
+ * Interrupt routine for the wake from the device
+ */
+static irqreturn_t bcm_bt_uart_isr(int irq, void *context)
+{
+ unsigned int bt_wake;
+ struct bcm_device *p = (struct bcm_device *)context;
+
+ bt_wake = gpiod_get_value(p->bt_wake_gpio);
+ BT_DBG("bcm_bt_uart_isr with bt_wake of %d (active_low %d), req bh",
+ bt_wake, p->bt_wake_active_low);
+
+ /* Defer the actual processing to the platform work queue */
+ schedule_work(&p->wakeup_work);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Device instance startup
+ */
+static int bcm_bt_uart_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct device_node *np = pdev->dev.of_node;
+ const char *tty_name;
+ struct bcm_device *p_bcm_device = NULL;
+
+ p_bcm_device = devm_kzalloc(&pdev->dev, sizeof(*p_bcm_device),
+ GFP_KERNEL);
+ if (!p_bcm_device) {
+ BT_DBG("bcm_bt_uart_probe - failing due to no memory");
+ return -ENOMEM;
+ }
+ p_bcm_device->pdev = pdev;
+ BT_DBG("bcm_bt_uart_probe %p context", p_bcm_device);
+
+ /* Get dev wake GPIO */
+ p_bcm_device->dev_wake_gpio = gpiod_get(&pdev->dev, "bt-wake");
+ BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-wake returned %p",
+ p_bcm_device->dev_wake_gpio);
+ if (IS_ERR(p_bcm_device->dev_wake_gpio)) {
+ ret = PTR_ERR(p_bcm_device->dev_wake_gpio);
+ if (ret != -ENOENT) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe - dev_wake GPIO: %d\n", ret);
+ }
+ p_bcm_device->dev_wake_gpio = NULL;
+ } else {
+ int resume_flag;
+
+ p_bcm_device->dev_wake_active_low = gpiod_is_active_low
+ (p_bcm_device->dev_wake_gpio);
+ BT_DBG("bcm_bt_uart_probe - dev_wake a-low is %d (cans %d)",
+ p_bcm_device->dev_wake_active_low,
+ gpiod_cansleep(p_bcm_device->dev_wake_gpio));
+
+ /* configure dev_wake as output with init resumed state */
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ ret = gpiod_direction_output(p_bcm_device->dev_wake_gpio,
+ resume_flag);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe s dev_wake GPIO: %d\n", ret);
+ gpiod_put(p_bcm_device->dev_wake_gpio);
+ p_bcm_device->dev_wake_gpio = NULL;
+ goto end;
+ } else {
+ BT_DBG("bcm_bt_uart_probe - dev_wake set to %d",
+ resume_flag);
+ }
+ }
+
+ /* Get power on/off GPIO */
+ p_bcm_device->reg_on_gpio = gpiod_get(&pdev->dev, "reg-on");
+ BT_DBG("bcm_bt_uart_probe - gpiod_get for reg-on returned %p",
+ p_bcm_device->reg_on_gpio);
+ if (IS_ERR(p_bcm_device->reg_on_gpio)) {
+ ret = PTR_ERR(p_bcm_device->reg_on_gpio);
+ if (ret != -ENOENT) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe - reg_on GPIO: %d\n", ret);
+ }
+ p_bcm_device->reg_on_gpio = NULL;
+ } else {
+ int poweron_flag;
+
+ p_bcm_device->reg_on_active_low = gpiod_is_active_low
+ (p_bcm_device->reg_on_gpio);
+ BT_DBG("bcm_bt_uart_probe - reg_on a-low is %d (cans %d)",
+ p_bcm_device->reg_on_active_low,
+ gpiod_cansleep(p_bcm_device->reg_on_gpio));
+
+ /* configure reg_on as output with init on state */
+ poweron_flag = !p_bcm_device->reg_on_active_low;
+ ret = gpiod_direction_output(p_bcm_device->reg_on_gpio,
+ poweron_flag);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe s reg_on GPIO: %d\n", ret);
+ gpiod_put(p_bcm_device->reg_on_gpio);
+ p_bcm_device->reg_on_gpio = NULL;
+ } else {
+ BT_DBG("bcm_bt_uart_probe - reg_on initially set to %d",
+ poweron_flag);
+ }
+ }
+
+ platform_set_drvdata(pdev, p_bcm_device);
+ /* Must be done before interrupt is requested */
+ INIT_WORK(&p_bcm_device->wakeup_work, bcm_bt_wakeup_task);
+
+ /* Get bt host wake GPIO */
+ p_bcm_device->bt_wake_gpio = gpiod_get(&pdev->dev, "bt-host-wake");
+ BT_DBG("bcm_bt_uart_probe - gpiod_get for bt-host-wake returned %p",
+ p_bcm_device->bt_wake_gpio);
+ if (IS_ERR(p_bcm_device->bt_wake_gpio)) {
+ ret = PTR_ERR(p_bcm_device->bt_wake_gpio);
+ if (ret != -ENOENT) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe - bt_wake GPIO: %d\n", ret);
+ }
+ p_bcm_device->bt_wake_gpio = NULL;
+ } else {
+ /* configure bt_wake as input */
+ ret = gpiod_direction_input(p_bcm_device->bt_wake_gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe s bt_wake GPIO: %d\n", ret);
+ gpiod_put(p_bcm_device->bt_wake_gpio);
+ p_bcm_device->bt_wake_gpio = NULL;
+ } else {
+ p_bcm_device->bt_wake_active_low = gpiod_is_active_low
+ (p_bcm_device->bt_wake_gpio);
+ BT_DBG("bcm_bt_uart_probe -bt_wake a-low is %d(cans%d)",
+ p_bcm_device->bt_wake_active_low,
+ gpiod_cansleep(p_bcm_device->bt_wake_gpio));
+ p_bcm_device->bt_wake_irq = gpiod_to_irq
+ (p_bcm_device->bt_wake_gpio);
+ if (p_bcm_device->bt_wake_irq < 0) {
+ dev_err(&pdev->dev,
+ "bcm_bt_uart_probe - HOST_WAKE IRQ: %d\n", ret);
+ } else {
+ unsigned long intflags = IRQF_TRIGGER_RISING;
+
+ if (p_bcm_device->bt_wake_active_low)
+ intflags = IRQF_TRIGGER_FALLING;
+
+ ret = request_irq(p_bcm_device->bt_wake_irq,
+ bcm_bt_uart_isr,
+ intflags, "bt_host_wake",
+ p_bcm_device);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "bcm_bt_uart_probe - failed to conf IRQ %d: %d",
+ p_bcm_device->bt_wake_irq, ret);
+ } else {
+ BT_DBG("bcm_bt_uart_probe - IRQ %d",
+ p_bcm_device->bt_wake_irq);
+ }
+ }
+ }
+ }
+
+ p_bcm_device->configure_sleep = 0;
+ if (!of_property_read_u32(np, "configure-sleep",
+ &p_bcm_device->configure_sleep)) {
+ BT_DBG("configure-sleep read as %d",
+ p_bcm_device->configure_sleep);
+ }
+ p_bcm_device->manual_fc = 0;
+ if (!of_property_read_u32(np, "manual-fc",
+ &p_bcm_device->manual_fc)) {
+ BT_DBG("manual-fc read as %d",
+ p_bcm_device->manual_fc);
+ }
+ p_bcm_device->baud_rate_before_config_download = 3000000;
+ if (!of_property_read_u32(
+ np, "baud-rate-before-config-download",
+ &p_bcm_device->baud_rate_before_config_download)) {
+ BT_DBG("baud-rate-before-config-download read as %d",
+ p_bcm_device->baud_rate_before_config_download);
+ }
+ p_bcm_device->configure_audio = 0;
+ if (!of_property_read_u32(np, "configure-audio",
+ &p_bcm_device->configure_audio)) {
+ BT_DBG("configure-audio read as %d",
+ p_bcm_device->configure_audio);
+ }
+ if (p_bcm_device->configure_audio) {
+ /* Defaults for audio */
+ p_bcm_device->PCMClockMode = 0;
+ p_bcm_device->PCMFillMethod = 2;
+ p_bcm_device->PCMFillNum = 0;
+ p_bcm_device->PCMFillValue = 3;
+ p_bcm_device->PCMInCallBitclock = 0;
+ p_bcm_device->PCMLSBFirst = 0;
+ p_bcm_device->PCMRightJustify = 0;
+ p_bcm_device->PCMRouting = 0;
+ p_bcm_device->PCMShortFrameSync = 0;
+ p_bcm_device->PCMSyncMode = 0;
+
+ if (!of_property_read_u32(np, "PCMClockMode",
+ &p_bcm_device->PCMClockMode))
+ BT_DBG("PCMClockMode read as %d",
+ p_bcm_device->PCMClockMode);
+ if (!of_property_read_u32(np, "PCMFillMethod",
+ &p_bcm_device->PCMFillMethod))
+ BT_DBG("PCMFillMethod readas %d",
+ p_bcm_device->PCMFillMethod);
+ if (!of_property_read_u32(np, "PCMFillNum",
+ &p_bcm_device->PCMFillNum))
+ BT_DBG("PCMFillNum read as %d",
+ p_bcm_device->PCMFillNum);
+ if (!of_property_read_u32(np, "PCMFillValue",
+ &p_bcm_device->PCMFillValue))
+ BT_DBG("PCMFillValue read as %d",
+ p_bcm_device->PCMFillValue);
+ if (!of_property_read_u32(np, "PCMInCallBitclock",
+ &p_bcm_device->PCMInCallBitclock))
+ BT_DBG("PCMInCallBitclock read as %d",
+ p_bcm_device->PCMInCallBitclock);
+ if (!of_property_read_u32(np, "PCMLSBFirst",
+ &p_bcm_device->PCMLSBFirst))
+ BT_DBG("PCMLSBFirst read as %d",
+ p_bcm_device->PCMLSBFirst);
+ if (!of_property_read_u32(np, "PCMRightJustify",
+ &p_bcm_device->PCMRightJustify))
+ BT_DBG("PCMRightJustify read as %d",
+ p_bcm_device->PCMRightJustify);
+ if (!of_property_read_u32(np, "PCMRouting",
+ &p_bcm_device->PCMRouting))
+ BT_DBG("PCMRouting read as %d",
+ p_bcm_device->PCMRouting);
+ if (!of_property_read_u32(np, "PCMShortFrameSync",
+ &p_bcm_device->PCMShortFrameSync))
+ BT_DBG("PCMShortFrameSync read as %d",
+ p_bcm_device->PCMShortFrameSync);
+ if (!of_property_read_u32(np, "PCMSyncMode",
+ &p_bcm_device->PCMSyncMode))
+ BT_DBG("PCMSyncMode read as %d",
+ p_bcm_device->PCMSyncMode);
+ }
+
+ if (!of_property_read_string(np, "tty", &tty_name)) {
+ strcpy(p_bcm_device->tty_name, tty_name);
+ BT_DBG("tty name read as %s", p_bcm_device->tty_name);
+ }
+
+ BT_DBG("idle_timeout set as %d", idle_timeout);
+
+ ret = 0; /* If we made it here, we're fine */
+
+ /* Place this instance on the device list */
+ spin_lock(&device_list_lock);
+ list_add_tail(&p_bcm_device->list, &device_list);
+ spin_unlock(&device_list_lock);
+
+end:
+ if (ret) {
+ if (p_bcm_device->reg_on_gpio) {
+ gpiod_put(p_bcm_device->reg_on_gpio);
+ p_bcm_device->reg_on_gpio = NULL;
+ }
+ if (p_bcm_device->bt_wake_gpio) {
+ gpiod_put(p_bcm_device->bt_wake_gpio);
+ p_bcm_device->bt_wake_gpio = NULL;
+ }
+ if (p_bcm_device->dev_wake_gpio) {
+ gpiod_put(p_bcm_device->dev_wake_gpio);
+ p_bcm_device->dev_wake_gpio = NULL;
+ }
+ }
+
+ BT_DBG("bcm_bt_uart_probe with the result %d", ret);
+ return ret;
+}
+
+/*
+ * Device instance removal
+ */
+static int bcm_bt_uart_remove(struct platform_device *pdev)
+{
+ struct bcm_device *p_bcm_device = platform_get_drvdata(pdev);
+
+ if (p_bcm_device == NULL) {
+ BT_DBG("bcm_bt_uart_remove - logic error, no probe?!");
+ return 0;
+ }
+
+ BT_DBG("bcm_bt_uart_remove %p context", p_bcm_device);
+
+ spin_lock(&device_list_lock);
+ list_del(&p_bcm_device->list);
+ spin_unlock(&device_list_lock);
+
+ BT_DBG("bcm_bt_uart_remove - freeing interrupt %d",
+ p_bcm_device->bt_wake_irq);
+ free_irq(p_bcm_device->bt_wake_irq, p_bcm_device);
+
+ if (p_bcm_device->reg_on_gpio) {
+ BT_DBG("bcm_bt_uart_remove - releasing reg_on_gpio");
+ gpiod_put(p_bcm_device->reg_on_gpio);
+ p_bcm_device->reg_on_gpio = NULL;
+ }
+
+ if (p_bcm_device->dev_wake_gpio) {
+ BT_DBG("bcm_bt_uart_remove - releasing dev_wake_gpio");
+ gpiod_put(p_bcm_device->dev_wake_gpio);
+ p_bcm_device->dev_wake_gpio = NULL;
+ }
+
+ if (p_bcm_device->bt_wake_gpio) {
+ BT_DBG("bcm_bt_uart_remove - releasing bt_wake_gpio");
+ gpiod_put(p_bcm_device->bt_wake_gpio);
+ p_bcm_device->bt_wake_gpio = NULL;
+ }
+
+ BT_DBG("bcm_bt_uart_remove %p done", p_bcm_device);
+ return 0;
+}
+
+/*
+ * Platform resume callback
+ */
+static int bcm_bt_uart_resume(struct device *pdev)
+{
+ int resume_flag;
+ struct bcm_device *p_bcm_device = platform_get_drvdata(
+ to_platform_device(pdev));
+
+ if (p_bcm_device == NULL) {
+ BT_DBG("bcm_bt_uart_resume - logic error, no device?!");
+ return 0;
+ }
+
+ BT_DBG("bcm_bt_uart_resume %p", p_bcm_device);
+
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ if (p_bcm_device->dev_wake_gpio) {
+ gpiod_set_value(p_bcm_device->dev_wake_gpio, resume_flag);
+ BT_DBG("bcm_bt_uart_resume: %d written, delaying 15 ms",
+ resume_flag);
+ mdelay(15);
+ }
+
+ /* Let the protocol know the platform is resuming */
+ if (p_bcm_device->protocol_callbacks.p_resume)
+ p_bcm_device->protocol_callbacks.p_resume(
+ p_bcm_device->protocol_callbacks.context);
+
+ return 0;
+}
+
+/*
+ * Platform suspend callback
+ */
+static int bcm_bt_uart_suspend(struct device *pdev)
+{
+ int resume_flag;
+ struct bcm_device *p_bcm_device = platform_get_drvdata(
+ to_platform_device(pdev));
+
+ if (p_bcm_device == NULL) {
+ BT_DBG("bcm_bt_uart_suspend - logic error, no device?!");
+ return 0;
+ }
+
+ BT_DBG("bcm_bt_uart_suspend %p", p_bcm_device);
+
+ /* Let the protocol know the platform is suspending */
+ if (p_bcm_device->protocol_callbacks.p_suspend)
+ p_bcm_device->protocol_callbacks.p_suspend(
+ p_bcm_device->protocol_callbacks.context);
+
+ /* Suspend the device */
+ if (p_bcm_device->dev_wake_gpio) {
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ gpiod_set_value(p_bcm_device->dev_wake_gpio, !resume_flag);
+ BT_DBG("bcm_bt_uart_suspend: %d written, delaying 15 ms",
+ !resume_flag);
+ mdelay(15);
+ }
+
+ return 0;
+}
+
+/*
+ * Entry point for calls from the protocol
+ */
+int btbcm_uart_control(int action, void *device_context,
+ void *p_data, unsigned long *p_size)
+{
+ struct btbcm_uart_callbacks *pc;
+ struct btbcm_uart_parameters *pp = p_data; /* for pars action only */
+ int ret = 0;
+ int resume_flag, poweron_flag;
+ struct bcm_device *p_bcm_device = device_context;
+ struct list_head *ptr;
+ bool is_found = false;
+
+ /* Special processing for the callback configuration */
+ if (action == BTBCM_UART_ACTION_CONFIGURE_CALLBACKS) {
+ pc = p_data;
+
+ BT_DBG("btbcm_uart_control - configure callbacks");
+ if (p_data == NULL || *p_size != sizeof(struct
+ btbcm_uart_callbacks) || (pc->interface_version !=
+ BTBCM_UART_INTERFACE_VERSION)) {
+ BT_DBG("btbcm_uart_control - callbacks mismatch!");
+ return -E2BIG;
+ }
+
+ BT_DBG("btbcm_uart_control - configure callbacks for %s(%p)",
+ pc->name, pc->context);
+ if (p_bcm_device == NULL) {
+ spin_lock(&device_list_lock);
+ list_for_each(ptr, &device_list) {
+ p_bcm_device = list_entry(ptr, struct
+ bcm_device, list);
+ if (!strcmp(p_bcm_device->tty_name, pc->name)) {
+ is_found = true;
+ break;
+ }
+ }
+
+ spin_unlock(&device_list_lock);
+ if (!is_found) {
+ BT_DBG("btbcm_uart_control - no device!");
+ return -ENOENT;
+ }
+ }
+
+ p_bcm_device->protocol_callbacks = *pc;
+ memcpy(p_data, &p_bcm_device, sizeof(p_bcm_device));
+ *p_size = sizeof(p_bcm_device);
+ return ret;
+ }
+
+ /* All other requests must have the right context */
+ if (p_bcm_device == NULL) {
+ BT_DBG("btbcm_uart_control - failing, no device");
+ return -ENOENT;
+ }
+
+ switch (action) {
+ case BTBCM_UART_ACTION_POWER_ON:
+ BT_DBG("btbcm_uart_control %p - power on", device_context);
+ if (p_bcm_device->reg_on_gpio) {
+ poweron_flag = !p_bcm_device->reg_on_active_low;
+ gpiod_set_value(p_bcm_device->reg_on_gpio,
+ poweron_flag);
+ BT_DBG("btbcm_uart_control - pwron %d, delay 15 ms",
+ poweron_flag);
+ mdelay(15);
+ }
+ break;
+
+ case BTBCM_UART_ACTION_POWER_OFF:
+ BT_DBG("btbcm_uart_control %p - power off", device_context);
+ if (p_bcm_device->reg_on_gpio) {
+ poweron_flag = p_bcm_device->reg_on_active_low;
+ gpiod_set_value(p_bcm_device->reg_on_gpio,
+ poweron_flag);
+ BT_DBG("btbcm_uart_control - pwroff %d, delay 15 ms",
+ poweron_flag);
+ mdelay(15);
+ }
+ break;
+
+ case BTBCM_UART_ACTION_RESUME:
+ BT_DBG("btbcm_uart_control %p - resume", device_context);
+ if (p_bcm_device->dev_wake_gpio) {
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ gpiod_set_value(p_bcm_device->dev_wake_gpio,
+ resume_flag);
+ BT_DBG("btbcm_uart_control - resume %d, delay 15 ms",
+ resume_flag);
+ mdelay(15);
+ }
+ break;
+
+ case BTBCM_UART_ACTION_SUSPEND:
+ BT_DBG("btbcm_uart_control %p - suspend", device_context);
+ if (p_bcm_device->dev_wake_gpio) {
+ resume_flag = !p_bcm_device->dev_wake_active_low;
+ gpiod_set_value(p_bcm_device->dev_wake_gpio,
+ !resume_flag);
+ BT_DBG("btbcm_uart_control - suspend %d, delay 15ms",
+ !resume_flag);
+ mdelay(15);
+ }
+ break;
+
+ case BTBCM_UART_ACTION_GET_PARAMETERS:
+ BT_DBG("btbcm_uart_control %p - get pars", device_context);
+ if ((p_data == NULL) ||
+ (*p_size < sizeof(struct btbcm_uart_parameters))) {
+ BT_DBG("btbcm_uart_control - failing, wrong par size");
+ return -E2BIG;
+ }
+
+ memset(pp, 0, sizeof(struct btbcm_uart_parameters));
+ pp->interface_version = BTBCM_UART_INTERFACE_VERSION;
+ pp->configure_sleep = p_bcm_device->configure_sleep;
+ pp->manual_fc = p_bcm_device->manual_fc;
+ pp->dev_wake_active_low = p_bcm_device->dev_wake_active_low;
+ pp->bt_wake_active_low = p_bcm_device->bt_wake_active_low;
+ pp->idle_timeout_in_secs = idle_timeout;
+ pp->baud_rate_before_config_download =
+ p_bcm_device->baud_rate_before_config_download;
+ pp->configure_audio = p_bcm_device->configure_audio;
+ pp->PCMClockMode = p_bcm_device->PCMClockMode;
+ pp->PCMFillMethod = p_bcm_device->PCMFillMethod;
+ pp->PCMFillNum = p_bcm_device->PCMFillNum;
+ pp->PCMFillValue = p_bcm_device->PCMFillValue;
+ pp->PCMInCallBitclock = p_bcm_device->PCMInCallBitclock;
+ pp->PCMLSBFirst = p_bcm_device->PCMLSBFirst;
+ pp->PCMRightJustify = p_bcm_device->PCMRightJustify;
+ pp->PCMRouting = p_bcm_device->PCMRouting;
+ pp->PCMShortFrameSync = p_bcm_device->PCMShortFrameSync;
+ pp->PCMSyncMode = p_bcm_device->PCMSyncMode;
+ *p_size = sizeof(struct btbcm_uart_parameters);
+ break;
+
+ default:
+ BT_DBG("btbcm_uart_control %p unknown act %d",
+ device_context, action);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(btbcm_uart_control);
+
+/* Platform susp and resume callbacks */
+static SIMPLE_DEV_PM_OPS(bcm_bt_uart_pm_ops,
+ bcm_bt_uart_suspend, bcm_bt_uart_resume);
+
+/* Driver match table */
+static const struct of_device_id bcm_bt_uart_table[] = {
+ { .compatible = "brcm,brcm-bt-uart" },
+ {}
+};
+
+/* Driver configuration */
+static struct platform_driver bcm_bt_uart_driver = {
+ .probe = bcm_bt_uart_probe,
+ .remove = bcm_bt_uart_remove,
+ .driver = {
+ .name = "brcm_bt_uart",
+ .of_match_table = of_match_ptr(bcm_bt_uart_table),
+ .owner = THIS_MODULE,
+ .pm = &bcm_bt_uart_pm_ops,
+ },
+};
+
+module_platform_driver(bcm_bt_uart_driver);
+
+MODULE_AUTHOR("Ilya Faenson");
+MODULE_DESCRIPTION("Broadcom Bluetooth UART Driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
diff --git a/drivers/bluetooth/btbcm_uart.h b/drivers/bluetooth/btbcm_uart.h
new file mode 100644
index 0000000..3b02a4e
--- /dev/null
+++ b/drivers/bluetooth/btbcm_uart.h
@@ -0,0 +1,116 @@
+/*
+ * Bluetooth BCM UART Driver Header
+ *
+ * Copyright (c) 2015 Broadcom Corporation
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 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.
+ *
+ * BSD LICENSE SUMMARY
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BTBCM_UART_H
+#define BTBCM_UART_H
+
+/* Change the version if you change anything in this header */
+#define BTBCM_UART_INTERFACE_VERSION 1
+/* Callbacks from the driver into the protocol */
+typedef void (*p_suspend_callback)(void *context);
+typedef void (*p_resume_callback)(void *context);
+typedef void (*p_wakeup_callback)(void *context);
+struct btbcm_uart_callbacks {
+ int interface_version; /* interface # compiled against */
+ void *context; /* protocol instance context */
+ char name[64]; /* protocol tty device, for example, ttyS0 */
+
+ /* client callbacks */
+ p_suspend_callback p_suspend;
+ p_resume_callback p_resume;
+ p_wakeup_callback p_wakeup;
+};
+
+/* Driver parameters retrieved from the DT or ACPI */
+struct btbcm_uart_parameters {
+ int interface_version; /* interface # compiled against */
+
+ /* Parameters themselves */
+ int configure_sleep;
+ int manual_fc;
+ int dev_wake_active_low;
+ int bt_wake_active_low;
+ int idle_timeout_in_secs;
+ int baud_rate_before_config_download;
+ int configure_audio;
+ int PCMClockMode;
+ int PCMFillMethod;
+ int PCMFillNum;
+ int PCMFillValue;
+ int PCMInCallBitclock;
+ int PCMLSBFirst;
+ int PCMRightJustify;
+ int PCMRouting;
+ int PCMShortFrameSync;
+ int PCMSyncMode;
+};
+
+/*
+ * Actions on the BTBCM_UART driver
+ */
+
+/* Configure protocol callbacks */
+#define BTBCM_UART_ACTION_CONFIGURE_CALLBACKS 0
+
+/* Retrieve BT device parameters */
+#define BTBCM_UART_ACTION_GET_PARAMETERS 1
+
+/* Resume the BT device via GPIO */
+#define BTBCM_UART_ACTION_RESUME 2
+
+/* Suspend the BT device via GPIO */
+#define BTBCM_UART_ACTION_SUSPEND 3
+
+/* Power the BT device off via GPIO */
+#define BTBCM_UART_ACTION_POWER_OFF 4
+
+/* Power the BT device on via GPIO */
+#define BTBCM_UART_ACTION_POWER_ON 5
+
+/* Execute an action on the BT device */
+extern int btbcm_uart_control(int action, void *device_context,
+ void *p_data, unsigned long *p_size);
+
+#endif
+
--
1.9.1


2015-05-13 21:50:23

by Ilya Faenson

[permalink] [raw]
Subject: [RFC v3 4/4] BlueZ Broadcom UART Protocol

Enhance Broadcom protocol with the UART setup, firmware download
and power management.

Signed-off-by: Ilya Faenson <[email protected]>
---
drivers/bluetooth/hci_bcm.c | 528 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 513 insertions(+), 15 deletions(-)

diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
index 1ec0b4a..ccd92ed 100644
--- a/drivers/bluetooth/hci_bcm.c
+++ b/drivers/bluetooth/hci_bcm.c
@@ -1,8 +1,9 @@
/*
*
- * Bluetooth HCI UART driver for Broadcom devices
+ * Bluetooth UART H4 protocol for Broadcom devices
*
* Copyright (C) 2015 Intel Corporation
+ * Copyright (C) 2015 Broadcom Corporation
*
*
* This program is free software; you can redistribute it and/or modify
@@ -21,48 +22,413 @@
*
*/

+#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/signal.h>
+#include <linux/ioctl.h>
#include <linux/skbuff.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

-#include "btbcm.h"
#include "hci_uart.h"
+#include "btbcm.h"
+#include "btbcm_uart.h"

struct bcm_data {
struct sk_buff *rx_skb;
struct sk_buff_head txq;
+ struct hci_uart *hu;
+
+ bool is_suspended; /* suspend/resume flag */
+
+ struct timer_list timer; /* idle timer */
+
+ struct btbcm_uart_parameters pars; /* device parameters */
+ void *device_context; /* ACPI/DT device context */
};

+/* Suspend/resume synchronization mutex */
+static DEFINE_MUTEX(plock);
+
+/*
+ * Callbacks from the BCMBT_UART device
+ */
+
+/*
+ * The platform is suspending. Stop UART activity
+ */
+static void suspend_notification(void *context)
+{
+ struct ktermios ktermios;
+ struct hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+ struct tty_struct *tty = hu->tty;
+ int status;
+ unsigned int set = 0;
+ unsigned int clear = 0;
+
+ BT_DBG("suspend_notification with is_suspended %d", h4->is_suspended);
+
+ if (!h4->pars.configure_sleep)
+ return;
+
+ if (!h4->is_suspended) {
+ if (h4->pars.manual_fc) {
+ /* Disable hardware flow control */
+ ktermios = tty->termios;
+ ktermios.c_cflag &= ~CRTSCTS;
+ status = tty_set_termios(tty, &ktermios);
+ if (status)
+ BT_DBG("suspend_notification dis fc fail %d",
+ status);
+ else
+ BT_DBG("suspend_notification hw fc disabled");
+
+ /* Clear RTS to prevent the device from sending */
+ /* (most PCs need OUT2 to enable interrupts) */
+ status = tty->driver->ops->tiocmget(tty);
+ BT_DBG("suspend_notification cur tiocm 0x%x", status);
+ set &= ~(TIOCM_OUT2 | TIOCM_RTS);
+ clear = ~set;
+ set &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ status = tty->driver->ops->tiocmset(tty, set, clear);
+ if (status)
+ BT_DBG("suspend_notification clr RTS fail %d",
+ status);
+ else
+ BT_DBG("suspend_notification RTS cleared");
+ status = tty->driver->ops->tiocmget(tty);
+ BT_DBG("suspend_notification end tiocm 0x%x", status);
+ }
+
+ /* Once this callback returns, driver suspends BT via GPIO */
+ h4->is_suspended = true;
+ }
+}
+
+/*
+ * The platform is resuming. Resume UART activity.
+ */
+static void resume_notification(void *context)
+{
+ struct ktermios ktermios;
+ struct hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+ struct tty_struct *tty = hu->tty;
+ int status;
+ unsigned int set = 0;
+ unsigned int clear = 0;
+
+ BT_DBG("resume_notification with is_suspended %d", h4->is_suspended);
+
+ if (!h4->pars.configure_sleep)
+ return;
+
+ /* When this callback executes, the device has woken up already */
+ if (h4->is_suspended) {
+ h4->is_suspended = false;
+
+ if (h4->pars.manual_fc) {
+ status = tty->driver->ops->tiocmget(tty);
+ BT_DBG("resume_notification cur tiocm 0x%x", status);
+ set |= (TIOCM_OUT2 | TIOCM_RTS);
+ clear = ~set;
+ set &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ status = tty->driver->ops->tiocmset(tty, set, clear);
+ if (status)
+ BT_DBG("resume_notification set RTS fail %d",
+ status);
+ else
+ BT_DBG("resume_notification RTS set");
+
+ /* Re-enable hardware flow control */
+ ktermios = tty->termios;
+ ktermios.c_cflag |= CRTSCTS;
+ status = tty_set_termios(tty, &ktermios);
+ if (status)
+ BT_DBG("resume_notification enable fc fail %d",
+ status);
+ else
+ BT_DBG("resume_notification hw fc re-enabled");
+ }
+ }
+
+ /* If we're resumed, the idle timer must be running */
+ status = mod_timer(&h4->timer, jiffies +
+ msecs_to_jiffies(h4->pars.idle_timeout_in_secs * 1000));
+}
+
+/*
+ * The BT device is resuming. Resume UART activity if suspended
+ */
+static void wakeup_notification(void *context)
+{
+ struct ktermios ktermios;
+ struct hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+ struct tty_struct *tty = hu->tty;
+ int status;
+ unsigned int set = 0;
+ unsigned int clear = 0;
+
+ if (!h4->pars.configure_sleep)
+ return;
+
+ status = tty->driver->ops->tiocmget(tty);
+ BT_DBG("wakeup_notification hu %p current tiocm 0x%x", hu, status);
+ if (h4->is_suspended) {
+ if (h4->pars.manual_fc) {
+ set |= (TIOCM_OUT2 | TIOCM_RTS);
+ clear = ~set;
+ set &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ clear &= TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 |
+ TIOCM_OUT2 | TIOCM_LOOP;
+ status = tty->driver->ops->tiocmset(tty, set, clear);
+ if (status)
+ BT_DBG("wakeup_notification set RTS fail %d",
+ status);
+ else
+ BT_DBG("wakeup_notification RTS set");
+
+ /* Re-enable hardware flow control */
+ ktermios = tty->termios;
+ ktermios.c_cflag |= CRTSCTS;
+ status = tty_set_termios(tty, &ktermios);
+ if (status)
+ BT_DBG("wakeup_notification fc-en failure %d",
+ status);
+ else
+ BT_DBG("wakeup_notification hw fc re-enabled");
+ }
+
+ h4->is_suspended = false;
+ }
+
+ /* If we're resumed, the idle timer must be running */
+ status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
+ h4->pars.idle_timeout_in_secs * 1000));
+}
+
+/*
+ * Make sure we're awake
+ * (called when the resumed state is required)
+ */
+static void bcm_ensure_wakeup(struct hci_uart *hu)
+{
+ struct bcm_data *h4 = hu->priv;
+ int status;
+
+ if (!h4->pars.configure_sleep)
+ return;
+
+ /* Suspend/resume operations are serialized */
+ mutex_lock(&plock);
+
+ /* Nothing to do if resumed already */
+ if (!h4->is_suspended) {
+ mutex_unlock(&plock);
+
+ /* Just reset the timer */
+ status = mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
+ h4->pars.idle_timeout_in_secs * 1000));
+ return;
+ }
+
+ /* Wakeup the device */
+ status = btbcm_uart_control(BTBCM_UART_ACTION_RESUME,
+ h4->device_context, NULL, NULL);
+ if (status)
+ BT_DBG("bcm_ensure_wakeup failed to resume driver %d", status);
+
+ /* Unflow control the port if configured */
+ resume_notification(hu);
+
+ mutex_unlock(&plock);
+}
+
+/*
+ * Idle timer callback
+ */
+static void bcm_idle_timeout(unsigned long arg)
+{
+ struct hci_uart *hu = (struct hci_uart *)arg;
+ struct bcm_data *h4 = hu->priv;
+ int status;
+
+ BT_DBG("bcm_idle_timeout hu %p", hu);
+
+ /* Suspend/resume operations are serialized */
+ mutex_lock(&plock);
+
+ if (!h4->is_suspended) {
+ /* Flow control the port if configured */
+ suspend_notification(hu);
+
+ /* Suspend the device */
+ status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
+ h4->device_context, NULL, NULL);
+ if (status)
+ BT_DBG("bcm_idle_timeout failed to suspend device %d",
+ status);
+ }
+
+ mutex_unlock(&plock);
+}
+
static int bcm_open(struct hci_uart *hu)
{
- struct bcm_data *bcm;
+ struct btbcm_uart_callbacks callbacks;
+ unsigned long callbacks_size = sizeof(callbacks);
+ int status;
+ struct bcm_data *h4;
+ struct tty_struct *tty = hu->tty;

- BT_DBG("hu %p", hu);
+ BT_DBG("bcm_h4_open hu %p", hu);

- bcm = kzalloc(sizeof(*bcm), GFP_KERNEL);
- if (!bcm)
+ h4 = kzalloc(sizeof(*h4), GFP_KERNEL);
+ if (!h4)
return -ENOMEM;

- skb_queue_head_init(&bcm->txq);
+ skb_queue_head_init(&h4->txq);
+ hu->priv = h4;
+ h4->hu = hu;
+ h4->is_suspended = false;
+
+ /* Configure callbacks on the driver */
+ callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
+ callbacks.context = hu;
+ strcpy(callbacks.name, tty->name);
+ callbacks.p_suspend = suspend_notification;
+ callbacks.p_resume = resume_notification;
+ callbacks.p_wakeup = wakeup_notification;
+ status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
+ NULL, &callbacks, &callbacks_size);
+ if (status) {
+ BT_DBG("bcm_h4_open failed to set driver callbacks %d", status);
+ return status;
+ }
+ if (callbacks_size != sizeof(void *)) {
+ BT_DBG("bcm_h4_open got back %d bytes from callbacks?!",
+ (int)callbacks_size);
+ return -EMSGSIZE;
+ }
+ memcpy(&h4->device_context, &callbacks, sizeof(void *));
+ BT_DBG("bcm_h4_open callbacks context %p", h4->device_context);
+
+ /* Retrieve device parameters */
+ callbacks_size = sizeof(h4->pars);
+ status = btbcm_uart_control(BTBCM_UART_ACTION_GET_PARAMETERS,
+ h4->device_context, &h4->pars,
+ &callbacks_size);
+ if (status) {
+ BT_DBG("bcm_h4_open failed to get dev parameters %d", status);
+ return status;
+ }
+ BT_DBG("Pars ver %d csleep %d dalow %d balow %d idle %d",
+ h4->pars.interface_version, h4->pars.configure_sleep,
+ h4->pars.dev_wake_active_low, h4->pars.bt_wake_active_low,
+ h4->pars.idle_timeout_in_secs);
+
+ /* Cycle power to make sure the device is in the known state */
+ status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
+ h4->device_context, NULL, NULL);
+ if (status) {
+ BT_DBG("bcm_h4_open failed to power off %d", status);
+ } else {
+ status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_ON,
+ h4->device_context, NULL, NULL);
+ if (status)
+ BT_DBG("bcm_h4_open failed to power on %d", status);
+ }
+
+ /* Start the idle timer */
+ if (h4->pars.configure_sleep) {
+ setup_timer(&h4->timer, bcm_idle_timeout, (unsigned long)hu);
+ if (h4->pars.configure_sleep)
+ mod_timer(&h4->timer, jiffies + msecs_to_jiffies(
+ h4->pars.idle_timeout_in_secs * 1000));
+ }

- hu->priv = bcm;
return 0;
}

static int bcm_close(struct hci_uart *hu)
{
- struct bcm_data *bcm = hu->priv;
+ struct btbcm_uart_callbacks callbacks;
+ unsigned long callbacks_size = sizeof(callbacks);
+ struct bcm_data *h4 = hu->priv;
+ int status;

- BT_DBG("hu %p", hu);
+ hu->priv = NULL;

- skb_queue_purge(&bcm->txq);
- kfree_skb(bcm->rx_skb);
- kfree(bcm);
+ BT_DBG("bcm_h4_close hu %p", hu);
+
+ /* If we're being closed, we must suspend */
+ if (h4->pars.configure_sleep) {
+ mutex_lock(&plock);
+
+ if (!h4->is_suspended) {
+ /* Flow control the port */
+ suspend_notification(hu);
+
+ /* Suspend the device */
+ status = btbcm_uart_control(BTBCM_UART_ACTION_SUSPEND,
+ h4->device_context, NULL,
+ NULL);
+ if (status) {
+ BT_DBG("bcm_h4_close suspend driver fail %d",
+ status);
+ }
+ }
+
+ mutex_unlock(&plock);
+
+ del_timer_sync(&h4->timer);
+ }
+
+ /* Power off the device if possible */
+ status = btbcm_uart_control(BTBCM_UART_ACTION_POWER_OFF,
+ h4->device_context, NULL, NULL);
+ if (status)
+ BT_DBG("bcm_h4_close failed to power off %d", status);
+
+ /* de-configure callbacks on the driver */
+ callbacks.interface_version = BTBCM_UART_INTERFACE_VERSION;
+ callbacks.context = hu;
+ callbacks.p_suspend = NULL;
+ callbacks.p_resume = NULL;
+ callbacks.p_wakeup = NULL;
+ status = btbcm_uart_control(BTBCM_UART_ACTION_CONFIGURE_CALLBACKS,
+ h4->device_context, &callbacks,
+ &callbacks_size);
+ if (status)
+ BT_DBG("bcm_h4_close failed to reset drv callbacks %d", status);
+ skb_queue_purge(&h4->txq);

hu->priv = NULL;
+ kfree(h4);
+
return 0;
}

@@ -79,11 +445,137 @@ static int bcm_flush(struct hci_uart *hu)

static int bcm_setup(struct hci_uart *hu)
{
- BT_DBG("hu %p", hu);
+ struct bcm_data *h4 = hu->priv;
+ int status;
+ struct sk_buff *skb;
+ unsigned char sleep_pars[] = {
+ 0x01, /* sleep mode 1=UART */
+ 0x02, /* idle threshold HOST (value * 300ms) */
+ 0x02, /* idle threshold HC (value * 300ms) */
+ 0x01, /* BT_WAKE active mode - 1=active high, 0 = active low */
+ 0x00, /* HOST_WAKE active mode - 1=active high, 0 = active low */
+ 0x01, /* Allow host sleep during SCO - FALSE */
+ 0x01, /* combine sleep mode and LPM - 1 == TRUE */
+ 0x00, /* enable tristate control of UART TX line - FALSE */
+ 0x00, /* USB auto-sleep on USB SUSPEND */
+ 0x00, /* USB USB RESUME timeout (seconds) */
+ 0x00, /* Pulsed Host Wake */
+ 0x00 /* Enable Break To Host */
+ };
+ unsigned char pcm_int_pars[] = {
+ 0x00, /* 0=PCM routing, 1=SCO over HCI */
+ 0x02, /* 0=128Kbps,1=256Kbps,2=512Kbps,3=1024Kbps,4=2048Kbps */
+ 0x00, /* short frame sync 0=short, 1=long */
+ 0x00, /* sync mode 0=slave, 1=master */
+ 0x00 /* clock mode 0=slave, 1=master */
+ };
+ unsigned char pcm_format_pars[] = {
+ 0x00, /* LSB_First 1=TRUE, 0=FALSE */
+ 0x00, /* Fill_Value (use 0-7 for fill bits value) */
+ 0x02, /* Fill_Method (2=sign extended) */
+ 0x03, /* Fill_Num # of fill bits 0-3) */
+ 0x01 /* Right_Justify 1=TRUE, 0=FALSE */
+ };
+ unsigned char time_slot_number = 0;
+
+ BT_DBG("bcm_h4_setup hu %p", hu);
+
+ /* Bring the UART into known default state */
+ status = btbcm_init_uart(hu);
+ if (status) {
+ BT_DBG("bcm_h4_setup failed to init BT device %d", status);
+ return status;
+ }
+
+ /* Basic sanity check */
+ skb = __hci_cmd_sync(hu->hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("bcm_h4_setup HCI Reset failed (%d)", status);
+ return status;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_h4_setup HCI Reset succeeded");
+
+ /* Set the new baud rate */
+ status = btbcm_set_baud_rate(hu,
+ h4->pars.baud_rate_before_config_download);
+ if (status) {
+ BT_ERR("bcm_h4_setup set_baud_rate faiilure %d", status);
+ return status;
+ }

hu->hdev->set_bdaddr = btbcm_set_bdaddr;

- return btbcm_setup_patchram(hu->hdev);
+ /* Download the firmware and reconfigure the UART afterwards */
+ status = btbcm_setup_patchram(hu->hdev);
+ if (status) {
+ BT_ERR("bcm_h4_setup setup_patchram faiilure %d", status);
+ return status;
+ }
+
+ /* Configure SCO PCM parameters */
+ if (h4->pars.configure_audio) {
+ pcm_int_pars[0] = h4->pars.PCMRouting;
+ pcm_int_pars[1] = h4->pars.PCMInCallBitclock;
+ pcm_int_pars[2] = h4->pars.PCMShortFrameSync;
+ pcm_int_pars[3] = h4->pars.PCMSyncMode;
+ pcm_int_pars[4] = h4->pars.PCMClockMode;
+ skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_pars),
+ pcm_int_pars, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("bcm_h4_setup PCM INT VSC failed (%d)", status);
+ return status;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_h4_setup PCM INT Parameters VSC succeeded");
+
+ pcm_format_pars[0] = h4->pars.PCMLSBFirst;
+ pcm_format_pars[1] = h4->pars.PCMFillValue;
+ pcm_format_pars[2] = h4->pars.PCMFillMethod;
+ pcm_format_pars[3] = h4->pars.PCMFillNum;
+ pcm_format_pars[4] = h4->pars.PCMRightJustify;
+ skb = __hci_cmd_sync(hu->hdev, 0xfc1e, sizeof(pcm_format_pars),
+ pcm_format_pars, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("bcm_h4_setup PCM Format VSC failed (%d)",
+ status);
+ return status;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_h4_setup PCM Format VSC succeeded");
+
+ skb = __hci_cmd_sync(hu->hdev, 0xfc22, sizeof(time_slot_number),
+ &time_slot_number, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("bcm_h4_setup SCO Time Slot VSC failed (%d)",
+ status);
+ return status;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_h4_setup SCO Time Slot VSC succeeded");
+ }
+
+ /* Configure device's suspend/resume operation */
+ if (h4->pars.configure_sleep) {
+ /* Override the default */
+ sleep_pars[3] = (unsigned char)!h4->pars.bt_wake_active_low;
+ sleep_pars[4] = (unsigned char)!h4->pars.dev_wake_active_low;
+ skb = __hci_cmd_sync(hu->hdev, 0xfc27, sizeof(sleep_pars),
+ sleep_pars, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ status = PTR_ERR(skb);
+ BT_ERR("bcm_h4_setup Sleep VSC failed (%d)", status);
+ return status;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_h4_setup Set Sleep Parameters VSC succeeded");
+ }
+
+ return 0;
}

static const struct h4_recv_pkt bcm_recv_pkts[] = {
@@ -99,6 +591,9 @@ static int bcm_recv(struct hci_uart *hu, const void *data, int count)
if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
return -EUNATCH;

+ /* Make sure we're resumed */
+ bcm_ensure_wakeup(hu);
+
bcm->rx_skb = h4_recv_buf(hu->hdev, bcm->rx_skb, data, count,
bcm_recv_pkts, ARRAY_SIZE(bcm_recv_pkts));
if (IS_ERR(bcm->rx_skb)) {
@@ -116,6 +611,9 @@ static int bcm_enqueue(struct hci_uart *hu, struct sk_buff *skb)

BT_DBG("hu %p skb %p", hu, skb);

+ /* Make sure we're resumed */
+ bcm_ensure_wakeup(hu);
+
/* Prepend skb with frame type */
memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
skb_queue_tail(&bcm->txq, skb);
--
1.9.1

2015-05-13 21:50:20

by Ilya Faenson

[permalink] [raw]
Subject: [RFC v3 1/4] Broadcom Bluetooth UART Device Tree bindings

Device Tree bindings to configure the Broadcom Bluetooth UART device.

Signed-off-by: Ilya Faenson <[email protected]>
---
.../devicetree/bindings/net/bluetooth/btbcm.txt | 54 ++++++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/bluetooth/btbcm.txt

diff --git a/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt b/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
new file mode 100644
index 0000000..cc9f225
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/btbcm.txt
@@ -0,0 +1,54 @@
+btbcm
+------
+
+Required properties:
+
+ - compatible : must be "brcm,brcm-bt-uart".
+ - tty : tty device connected to this Bluetooth device.
+
+Optional properties:
+
+ - bt-host-wake-gpios : bt-host-wake input GPIO to be used as an interrupt.
+
+ - bt-wake-gpios : bt-wake output GPIO to be used to suspend / resume device.
+
+ - reg-on-gpios : reg-on output GPIO to be used to power device on/off.
+
+ - baud-rate-before-config-download : initial Bluetooth device baud rate.
+ Default: 3000000.
+
+ - manual-fc : flow control UART in suspend / resume scenarios.
+ Default: 0.
+
+ - configure-sleep : configure suspend / resume flag.
+ Default: 0.
+
+ - configure-audio : configure platform PCM SCO flag.
+ Default: 0.
+
+ - PCM* : SCO PCM platform parameters. Work with Broadcom on setting.
+ Defaults: see the example below.
+
+
+Example:
+
+ bcm4354_bt_uart: bcm4354-bt-uart {
+ compatible = "bcm-bt-uart,bcm4354-bt-uart";
+ bt-wake-gpios = <&gpio4 30 GPIO_ACTIVE_HIGH>;
+ bt-host-wake-gpios = <&gpio4 31 GPIO_ACTIVE_HIGH>;
+ tty = "ttyS0";
+ baud-rate-before-config-download = <3000000>;
+ configure-sleep = <1>;
+ configure-audio = <1>;
+ PCMClockMode = <0>;
+ PCMFillMethod = <2>;
+ PCMFillNum = <0>;
+ PCMFillValue = <3>;
+ PCMInCallBitclock = <0>;
+ PCMLSBFirst = <0>;
+ PCMRightJustify = <0>;
+ PCMRouting = <0>;
+ PCMShortFrameSync = <0>;
+ PCMSyncMode = <0>;
+ };
+
--
1.9.1