All of lore.kernel.org
 help / color / mirror / Atom feed
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 v2 5/5] BlueZ Broadcom UART Protocol
Date: Wed, 10 Jun 2015 16:05:20 -0400	[thread overview]
Message-ID: <1433966720-17482-6-git-send-email-ifaenson@broadcom.com> (raw)
In-Reply-To: <1433966720-17482-1-git-send-email-ifaenson@broadcom.com>

Merge Broadcom protocol with the existing implementation, providing
for UART setup, firmware download and power management.

Signed-off-by: Ilya Faenson <ifaenson@broadcom.com>
---
 drivers/bluetooth/hci_bcm.c | 400 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 384 insertions(+), 16 deletions(-)

diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c
index e4d66b6..5ff35b7 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,170 @@
 
 #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_parameters pars; /* device parameters */
+	void *device_context; /* ACPI/DT device context */
 };
 
+/* Suspend/resume synchronization mutex */
+static DEFINE_MUTEX(plock);
+
+/* Forward reference */
+static struct hci_uart_proto bcm_proto;
+
+/*
+ * 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("%s: is_suspended %d", __func__, h4->is_suspended);
+
+	if (!h4->pars.configure_sleep)
+		return;
+
+	if (!h4->is_suspended) {
+		if (h4->pars.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("%s: is_suspended %d", __func__, 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)
+			hci_uart_set_flow_control(hu, false);
+	}
+
+	/* If we're resumed, the idle timer must be running */
+	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 hci_uart *hu = (struct hci_uart *)context;
+	struct bcm_data *h4 = hu->priv;
+
+	BT_DBG("%s: is_suspended %d", __func__, h4->is_suspended);
+
+	if (!h4->pars.configure_sleep)
+		return;
+
+	if (h4->is_suspended) {
+		if (h4->pars.manual_fc)
+			hci_uart_set_flow_control(hu, false);
+
+		h4->is_suspended = false;
+	}
+
+	/* If we're resumed, the idle timer must be running */
+	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("%s: failed to resume driver %d", __func__, 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("%s: hu %p", __func__, 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("%s: failed to suspend device %d", __func__,
+			       status);
+	}
+
+	mutex_unlock(&plock);
+}
+
 static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
 {
 	struct hci_dev *hdev = hu->hdev;
@@ -88,31 +248,138 @@ 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;
+	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_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_open failed to set driver callbacks %d", status);
+		return status;
+	}
+	if (callbacks_size != sizeof(void *)) {
+		BT_DBG("bcm_open got back %d bytes from callbacks?!",
+		       (int)callbacks_size);
+		return -EMSGSIZE;
+	}
+	memcpy(&h4->device_context, &callbacks, sizeof(void *));
+	BT_DBG("bcm_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_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);
+	bcm_proto.oper_speed = h4->pars.oper_speed;
+
+	/* 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_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_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_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_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_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_close failed to reset drv callbacks %d", status);
+	skb_queue_purge(&h4->txq);
 
 	hu->priv = NULL;
+	kfree(h4);
+
 	return 0;
 }
 
@@ -129,11 +396,42 @@ 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;
-
-	BT_DBG("hu %p", hu);
+	struct sk_buff *skb;
+	struct bcm_data *h4 = hu->priv;
+	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_setup hu %p", hu);
 
 	hu->hdev->set_bdaddr = btbcm_set_bdaddr;
 
@@ -162,6 +460,67 @@ static int bcm_setup(struct hci_uart *hu)
 			hci_uart_set_baudrate(hu, hu->proto->oper_speed);
 	}
 
+	/* Configure SCO PCM parameters */
+	if (h4->pars.configure_audio) {
+		pcm_int_pars[0] = h4->pars.pcm_routing;
+		pcm_int_pars[1] = h4->pars.pcm_incallbitclock;
+		pcm_int_pars[2] = h4->pars.pcm_shortframesync;
+		pcm_int_pars[3] = h4->pars.pcm_syncmode;
+		pcm_int_pars[4] = h4->pars.pcm_clockmode;
+		skb = __hci_cmd_sync(hu->hdev, 0xfc1c, sizeof(pcm_int_pars),
+				     pcm_int_pars, 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");
+
+		pcm_format_pars[0] = h4->pars.pcm_lsbfirst;
+		pcm_format_pars[1] = h4->pars.pcm_fillvalue;
+		pcm_format_pars[2] = h4->pars.pcm_fillmethod;
+		pcm_format_pars[3] = h4->pars.pcm_fillnum;
+		pcm_format_pars[4] = h4->pars.pcm_rightjustify;
+		skb = __hci_cmd_sync(hu->hdev, 0xfc1e, sizeof(pcm_format_pars),
+				     pcm_format_pars, 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->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)) {
+			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:
 	release_firmware(fw);
 
@@ -183,6 +542,11 @@ static int bcm_recv(struct hci_uart *hu, const void *data, int count)
 	if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
 		return -EUNATCH;
 
+	BT_DBG("bcm_recv %d bytes", count);
+
+	/* 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)) {
@@ -198,7 +562,10 @@ static int bcm_enqueue(struct hci_uart *hu, struct sk_buff *skb)
 {
 	struct bcm_data *bcm = hu->priv;
 
-	BT_DBG("hu %p skb %p", hu, skb);
+	BT_DBG("bcm_enqueue 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);
@@ -214,7 +581,8 @@ static struct sk_buff *bcm_dequeue(struct hci_uart *hu)
 	return skb_dequeue(&bcm->txq);
 }
 
-static const struct hci_uart_proto bcm_proto = {
+/* This structure may not be const as speed may change at runtime */
+static struct hci_uart_proto bcm_proto = {
 	.id		= HCI_UART_BCM,
 	.name		= "BCM",
 	.init_speed	= 115200,
-- 
1.9.1

  parent reply	other threads:[~2015-06-10 20:05 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-10 20:05 [PATCH v2 0/5] Broadcom Bluetooth UART device driver Ilya Faenson
2015-06-10 20:05 ` [PATCH v2 1/5] Broadcom Bluetooth UART Device Tree bindings Ilya Faenson
2015-06-11  9:39   ` Marcel Holtmann
2015-06-12 15:32     ` Ilya Faenson
2015-06-12 15:39       ` Marcel Holtmann
2015-06-12 16:51         ` Ilya Faenson
2015-06-10 20:05 ` [PATCH v2 2/5] H4 line discipline enhancements Ilya Faenson
2015-06-11 10:05   ` Marcel Holtmann
2015-06-12 15:36     ` Ilya Faenson
2015-06-10 20:05 ` [PATCH v2 3/5] Broadcom Bluetooth UART Platform Driver Ilya Faenson
2015-06-12  8:45   ` Marcel Holtmann
2015-06-12 16:26     ` Ilya Faenson
2015-06-13  8:04       ` Marcel Holtmann
2015-06-13 14:12         ` Ilya Faenson
2015-06-10 20:05 ` [PATCH v2 4/5] Support the BCM4354 Bluetooth UART device Ilya Faenson
2015-06-12  8:47   ` Marcel Holtmann
2015-06-12 16:26     ` Ilya Faenson
2015-06-10 20:05 ` Ilya Faenson [this message]
2015-06-12  9:31   ` [PATCH v2 5/5] BlueZ Broadcom UART Protocol Marcel Holtmann
2015-06-12 16:47     ` Ilya Faenson
2015-06-13  7:53       ` Marcel Holtmann
2015-06-13 14:10         ` Ilya Faenson
2015-06-15  9:34           ` Frederic Danis
2015-06-15 10:04             ` Marcel Holtmann
2015-06-12  8:27 ` [PATCH v2 0/5] Broadcom Bluetooth UART device driver Loic Poulain
2015-06-12 15:38   ` Ilya Faenson

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=1433966720-17482-6-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.