From: Ilya Faenson <ifaenson@broadcom.com>
To: Marcel Holtmann <marcel@holtmann.org>
Cc: <linux-bluetooth@vger.kernel.org>,
Arend van Spriel <arend@broadcom.com>,
Ilya Faenson <ifaenson@broadcom.com>
Subject: [PATCH v4 4/4] hci_bcm: Broadcom UART protocol enhancements
Date: Wed, 17 Jun 2015 17:30:58 -0400 [thread overview]
Message-ID: <1434576658-20730-5-git-send-email-ifaenson@broadcom.com> (raw)
In-Reply-To: <1434576658-20730-1-git-send-email-ifaenson@broadcom.com>
Merge Broadcom protocol with the existing implementation, providing:
UART setup
Idle suspend
Suspend/resume upon platform suspend/resume
Integration with the Broadcom platform device
PCM configuration
Signed-off-by: Ilya Faenson <ifaenson@broadcom.com>
---
drivers/bluetooth/hci_bcm.c | 340 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 332 insertions(+), 8 deletions(-)
diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
index aa3c9ac..bfe04df 100644
--- a/drivers/bluetooth/hci_bcm.c
+++ b/drivers/bluetooth/hci_bcm.c
@@ -3,6 +3,7 @@
* Bluetooth HCI UART driver 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
@@ -24,6 +25,7 @@
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
+#include <linux/tty.h>
#include <linux/firmware.h>
#include <net/bluetooth/bluetooth.h>
@@ -31,12 +33,161 @@
#include "btbcm.h"
#include "hci_uart.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_params params; /* device parameters */
+ void *device_context; /* ACPI/DT device context */
};
+/* Suspend/resume synchronization mutex */
+static DEFINE_MUTEX(power_lock);
+
+/* Useful timer restart helper */
+static void restart_idle_timer(struct hci_uart *hu)
+{
+ struct bcm_data *h4 = hu->priv;
+
+ mod_timer(&h4->timer, jiffies +
+ msecs_to_jiffies(h4->params.idle_timeout_in_secs * 1000));
+}
+
+/* Callbacks from the BCMBT_UART device
+ */
+
+/* The platform is suspending. Stop UART activity */
+static void suspend_notification(void *context)
+{
+ struct hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+
+ BT_DBG("suspend: is_suspended %d", h4->is_suspended);
+
+ if (!h4->params.configure_sleep)
+ return;
+
+ if (!h4->is_suspended) {
+ if (h4->params.manual_fc)
+ hci_uart_set_flow_control(hu, true);
+
+ /* 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 hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+
+ BT_DBG("resume: is_suspended %d", h4->is_suspended);
+
+ if (!h4->params.configure_sleep)
+ return;
+
+ /* When this callback executes, the device has woken up already */
+ if (h4->is_suspended) {
+ h4->is_suspended = false;
+
+ if (h4->params.manual_fc)
+ hci_uart_set_flow_control(hu, false);
+ }
+
+ /* If we're resumed, the idle timer must be running */
+ restart_idle_timer(hu);
+}
+
+/*
+ * The BT device is resuming. Resume UART activity if suspended
+ */
+static void wakeup_notification(void *context)
+{
+ struct hci_uart *hu = (struct hci_uart *)context;
+ struct bcm_data *h4 = hu->priv;
+
+ BT_DBG("wakeup: is_suspended %d", h4->is_suspended);
+
+ if (!h4->params.configure_sleep)
+ return;
+
+ if (h4->is_suspended) {
+ if (h4->params.manual_fc)
+ hci_uart_set_flow_control(hu, false);
+
+ h4->is_suspended = false;
+ }
+
+ /* If we're resumed, the idle timer must be running */
+ restart_idle_timer(hu);
+}
+
+/*
+ * 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;
+
+ if (!h4->params.configure_sleep)
+ return;
+
+ /* Suspend/resume operations are serialized */
+ mutex_lock(&power_lock);
+
+ /* Nothing to do if resumed already */
+ if (!h4->is_suspended) {
+ mutex_unlock(&power_lock);
+
+ /* Just reset the timer */
+ restart_idle_timer(hu);
+ return;
+ }
+
+ /* Wakeup the device */
+ btbcm_uart_resume(h4->device_context);
+
+ /* Unflow control the port if configured */
+ resume_notification(hu);
+
+ mutex_unlock(&power_lock);
+}
+
+/*
+ * 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;
+
+ BT_DBG("idle_timeout: hu %p", hu);
+
+ /* Suspend/resume operations are serialized */
+ mutex_lock(&power_lock);
+
+ if (!h4->is_suspended) {
+ /* Flow control the port if configured */
+ suspend_notification(hu);
+
+ /* Suspend the device */
+ btbcm_uart_suspend(h4->device_context);
+ }
+
+ mutex_unlock(&power_lock);
+}
+
static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
{
struct hci_dev *hdev = hu->hdev;
@@ -89,6 +240,8 @@ static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
static int bcm_open(struct hci_uart *hu)
{
struct bcm_data *bcm;
+ struct btbcm_uart_callbacks callbacks;
+ struct tty_struct *tty = hu->tty;
BT_DBG("hu %p", hu);
@@ -99,6 +252,48 @@ static int bcm_open(struct hci_uart *hu)
skb_queue_head_init(&bcm->txq);
hu->priv = bcm;
+ bcm->hu = hu;
+ bcm->is_suspended = false;
+ memset(&bcm->params, 0, sizeof(bcm->params));
+
+ /* Reset UART to a default state */
+ hci_uart_init_tty(hu);
+
+ /* Configure callbacks on the driver */
+ callbacks.context = hu;
+ strcpy(callbacks.name, tty->name);
+ callbacks.suspend = suspend_notification;
+ callbacks.resume = resume_notification;
+ callbacks.wakeup = wakeup_notification;
+ bcm->device_context = btbcm_uart_set_callbacks(&callbacks);
+ if (!bcm->device_context) {
+ /* That is likely an indication of no bcm driver present */
+ BT_DBG("Callback set failure (no driver present)");
+
+ /* Using speed defaults */
+ hci_uart_set_speeds(hu, 115200, 3000000);
+ return 0;
+ }
+
+ /* Retrieve device parameters */
+ btbcm_uart_get_params(bcm->device_context, &bcm->params);
+ BT_DBG("Context %p conf_sleep %d dev_actlow %d bt_actlow %d idle_t %d",
+ bcm->device_context, bcm->params.configure_sleep,
+ bcm->params.dev_wake_active_low, bcm->params.bt_wake_active_low,
+ bcm->params.idle_timeout_in_secs);
+ hci_uart_set_speeds(hu, 115200, bcm->params.oper_speed);
+
+ /* Cycle power to make sure the device is in the known state */
+ btbcm_uart_poweroff(bcm->device_context);
+ btbcm_uart_poweron(bcm->device_context);
+
+ /* Start the idle timer */
+ if (bcm->params.configure_sleep) {
+ setup_timer(&bcm->timer, bcm_idle_timeout, (unsigned long)hu);
+ if (bcm->params.configure_sleep)
+ restart_idle_timer(hu);
+ }
+
return 0;
}
@@ -108,6 +303,31 @@ static int bcm_close(struct hci_uart *hu)
BT_DBG("hu %p", hu);
+ /* If we're being closed, we must suspend */
+ if (bcm->params.configure_sleep) {
+ mutex_lock(&power_lock);
+
+ if (!bcm->is_suspended) {
+ /* Flow control the port */
+ suspend_notification(hu);
+
+ /* Suspend the device */
+ btbcm_uart_suspend(bcm->device_context);
+ }
+
+ mutex_unlock(&power_lock);
+
+ del_timer_sync(&bcm->timer);
+ }
+
+ /* Power off the device if possible */
+ if (bcm->device_context) {
+ btbcm_uart_poweroff(bcm->device_context);
+
+ /* de-configure callbacks on the driver */
+ btbcm_uart_reset_callbacks(bcm->device_context);
+ }
+
skb_queue_purge(&bcm->txq);
kfree_skb(bcm->rx_skb);
kfree(bcm);
@@ -129,9 +349,12 @@ static int bcm_flush(struct hci_uart *hu)
static int bcm_setup(struct hci_uart *hu)
{
- char fw_name[64];
+ char fw_name[128];
const struct firmware *fw;
int err;
+ struct sk_buff *skb;
+ struct bcm_data *h4 = hu->priv;
+ unsigned char time_slot_number = 0;
BT_DBG("hu %p", hu);
@@ -153,13 +376,110 @@ static int bcm_setup(struct hci_uart *hu)
goto finalize;
}
- if (hu->proto->init_speed)
- hci_uart_set_baudrate(hu, hu->proto->init_speed);
+ if (hu->init_speed)
+ hci_uart_set_baudrate(hu, hu->init_speed);
- if (hu->proto->oper_speed) {
- err = bcm_set_baudrate(hu, hu->proto->oper_speed);
+ if (hu->oper_speed) {
+ err = bcm_set_baudrate(hu, hu->oper_speed);
if (!err)
- hci_uart_set_baudrate(hu, hu->proto->oper_speed);
+ hci_uart_set_baudrate(hu, hu->oper_speed);
+ }
+
+ /* Configure SCO PCM parameters */
+ if (h4->params.configure_audio) {
+ struct bcm_set_pcm_int_params pcm_int_params;
+ struct bcm_set_pcm_format_params pcm_format_params;
+
+ /* 0=PCM routing, 1=SCO over HCI */
+ pcm_int_params.routing = h4->params.pcm_routing;
+ /* 0=128Kbps,1=256Kbps,2=512Kbps,3=1024Kbps,4=2048Kbps */
+ pcm_int_params.rate = h4->params.pcm_incallbitclock;
+ /* short frame sync 0=short, 1=long */
+ pcm_int_params.frame_sync = h4->params.pcm_shortframesync;
+ /* sync mode 0=slave, 1=master */
+ pcm_int_params.sync_mode = h4->params.pcm_syncmode;
+ /* clock mode 0=slave, 1=master */
+ pcm_int_params.clock_mode = h4->params.pcm_clockmode;
+
+ skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_params),
+ &pcm_int_params, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("bcm_setup PCM INT VSC failed (%d)", err);
+ goto finalize;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_setup PCM INT Parameters VSC succeeded");
+
+ /* LSB_First 1=TRUE, 0=FALSE */
+ pcm_format_params.lsb_first = h4->params.pcm_lsbfirst;
+ /* Fill_Value (use 0-7 for fill bits value) */
+ pcm_format_params.fill_value = h4->params.pcm_fillvalue;
+ /* Fill_Method (2=sign extended) */
+ pcm_format_params.fill_method = h4->params.pcm_fillmethod;
+ /* Fill_Num # of fill bits 0-3) */
+ pcm_format_params.fill_num = h4->params.pcm_fillnum;
+ /* Right_Justify 1=TRUE, 0=FALSE */
+ pcm_format_params.right_justify = h4->params.pcm_rightjustify;
+
+ skb = __hci_cmd_sync(hu->hdev, 0xfc1e,
+ sizeof(pcm_format_params),
+ &pcm_format_params, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("bcm_setup PCM Format VSC failed (%d)",
+ err);
+ goto finalize;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_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)) {
+ err = PTR_ERR(skb);
+ BT_ERR("bcm_setup SCO Time Slot VSC failed (%d)",
+ err);
+ goto finalize;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_setup SCO Time Slot VSC succeeded");
+ }
+
+ /* Configure device's suspend/resume operation */
+ if (h4->params.configure_sleep) {
+ struct bcm_set_sleep_mode sleep_params;
+
+ sleep_params.sleep_mode = 1; /* UART */
+ sleep_params.idle_host = 2; /* idle threshold HOST, in 300ms */
+ sleep_params.idle_dev = 2; /* idle threshold device, in 300ms */
+ /* BT_WAKE active mode - 1=active high, 0 = active low */
+ sleep_params.bt_wake_active = (unsigned char)
+ !h4->params.bt_wake_active_low;
+ /* HOST_WAKE active mode - 1=active high, 0 = active low */
+ sleep_params.host_wake_active = (unsigned char)
+ !h4->params.dev_wake_active_low;
+ /* Allow host sleep in SCO flag */
+ sleep_params.allow_host_sleep = 1;
+ /* Combine sleep and LPM flag */
+ sleep_params.combine_modes = 1;
+ /** Allow tristate control of UART tx flag */
+ sleep_params.tristate_control = 0;
+ /* Irrelevant USB flags */
+ sleep_params.usb_auto_sleep = 0;
+ sleep_params.usb_resume_timeout = 0;
+ sleep_params.pulsed_host_wake = 0;
+ sleep_params.break_to_host = 0;
+
+ skb = __hci_cmd_sync(hu->hdev, 0xfc27, sizeof(sleep_params),
+ &sleep_params, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("bcm_setup Sleep VSC failed (%d)", err);
+ goto finalize;
+ }
+ kfree_skb(skb);
+ BT_DBG("bcm_setup Set Sleep Parameters VSC succeeded");
}
finalize:
@@ -183,6 +503,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)) {
@@ -201,6 +524,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);
@@ -218,8 +544,6 @@ static struct sk_buff *bcm_dequeue(struct hci_uart *hu)
static const struct hci_uart_proto bcm_proto = {
.id = HCI_UART_BCM,
.name = "BCM",
- .init_speed = 115200,
- .oper_speed = 4000000,
.open = bcm_open,
.close = bcm_close,
.flush = bcm_flush,
--
1.9.1
prev parent reply other threads:[~2015-06-17 21:30 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-06-17 21:30 [PATCH v4 0/4] Broadcom Bluetooth UART device driver Ilya Faenson
2015-06-17 21:30 ` [PATCH v4 1/4] Broadcom Bluetooth UART Device Tree bindings Ilya Faenson
2015-06-17 21:33 ` Arend van Spriel
2015-06-17 23:11 ` FW: " Ilya Faenson
2015-06-18 15:02 ` Rob Herring
2015-06-18 18:54 ` Ilya Faenson
2015-06-18 19:41 ` Rob Herring
2015-06-18 20:37 ` Ilya Faenson
2015-06-19 15:47 ` Rob Herring
2015-06-19 17:06 ` Arend van Spriel
2015-06-19 18:49 ` Rob Herring
2015-06-19 19:20 ` Arend van Spriel
2015-06-29 18:36 ` Arend van Spriel
2015-07-02 18:26 ` Rob Herring
2015-06-19 20:37 ` Ilya Faenson
2015-06-19 19:23 ` Fabio Estevam
2015-06-19 20:40 ` Ilya Faenson
2015-06-17 21:30 ` [PATCH v4 2/4] hci_uart: line discipline enhancements Ilya Faenson
2015-06-17 21:50 ` Marcel Holtmann
2015-06-17 21:53 ` Ilya Faenson
2015-06-18 9:46 ` Frederic Danis
2015-06-18 10:17 ` Marcel Holtmann
2015-06-17 21:30 ` [PATCH v4 3/4] btbcm_uart: Broadcom UART Platform Driver Ilya Faenson
2015-06-17 21:30 ` Ilya Faenson [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1434576658-20730-5-git-send-email-ifaenson@broadcom.com \
--to=ifaenson@broadcom.com \
--cc=arend@broadcom.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=marcel@holtmann.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).