2008-12-19 19:14:15

by Nick Pelly

[permalink] [raw]
Subject: [RFC] Control of uart clock from tty client


On embedded architectures we need to clk_enable()/clk_disable() the
bluetooth uart clock when the bluetooth chip enters/exits low power
mode, to reduce power usage.

This needs to occur independently of the linux cpu suspend/resume
paths, because many bluetooth chips (TI BRF6300 in our case) have
power states that transition independently of the linux cpu: we need
to be able to turn the BT uart off while the linux CPU is running, and
leave the BT uart on while the linux CPU is in suspend.

The BT power state is known in drivers/bluetooth/hci_ll.c which reads
headers on the BT HCI traffic to determine what power state the BT
chip is in.

Our current implementation makes calls directly from hci_ll.c into our
uart driver to tell it when it can clk_enable() / clk_disable().
Sample patch is attached.

We know that calling the uart driver directly from hci_ll.c is
unlikely to get merged upstream, because we have by-passed the tty and
serial_core layers. Are there any suggestions on how to plumb clock
control from hci_ll.c to our uart driver in a way that could get
merged upstream?

Would probably need to use an existing uart_ops (serial_core.h). But
none seem suitable:
.ioctl uses a mutex, but we cannot sleep from hci_ll.c
.pm is also called during suspend / resume
.set_termios using a baud rate of zero to clk_disable() seems
workable, but how to clk_enable() ?

Anyway here is a sample patch. I've just shown how hci_ll.c calls the
msm_serial_clock_*() interface we introduced. The implementation of
msm_serial_clock_*() in msm_serial.c is fairly hefty because of our
requirement to flush the tx path before clk_disable(), and
msm_serial.c is not yet in mainline linux, so for this side of the
implementation please see:

Requesting comments on how to implement this clock control in a way
that could get merged upstream.

commit 16be2941c59b3fe089149b4cf15543
Author: Nick Pelly <[email protected]>
Date: Mon Jun 30 16:41:58 2008 -0700

bluetooth: use msm_serial clock control for power management of bt uart.

Signed-off-by: Nick Pelly <[email protected]>

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 075598e..f99d89d 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -90,6 +90,16 @@ config BT_HCIUART_LL

Say Y here to compile support for HCILL protocol.

+ bool "Make msm serial clock requests when the BT chip sleeps."
+ default n
+ help
+ Sends requests to msm_serial to turn the uart clock on and off as the
+ Bluetooth chip enters/exists deep sleep, providing power savings.
+ This requires that the tty layer for bluetooth uses the msm_serial
+ driver.
config BT_HCIBCM203X
tristate "HCI BCM203x USB driver"
depends on USB
diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c
index b91d45a..6c4d698 100644
--- a/drivers/bluetooth/hci_ll.c
+++ b/drivers/bluetooth/hci_ll.c
@@ -45,6 +45,8 @@
#include <linux/signal.h>
#include <linux/ioctl.h>
#include <linux/skbuff.h>
+#include <linux/serial_core.h>
+#include <linux/tty.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
@@ -173,6 +175,28 @@ static int ll_close(struct hci_uart *hu)
return 0;

+void msm_serial_clock_on(struct uart_port *port);
+void msm_serial_clock_request_off(struct uart_port *port);
+static void __ll_msm_serial_clock_on(struct tty_struct *tty) {
+ struct uart_state *state = tty->driver_data;
+ struct uart_port *port = state->port;
+ msm_serial_clock_on(port);
+static void __ll_msm_serial_clock_request_off(struct tty_struct *tty) {
+ struct uart_state *state = tty->driver_data;
+ struct uart_port *port = state->port;
+ msm_serial_clock_request_off(port);
+static inline void __ll_msm_serial_clock_on(struct tty_struct *tty) {}
+static inline void __ll_msm_serial_clock_request_off(struct tty_struct *tty) {}
* internal function, which does common work of the device wake up process:
* 1. places all pending packets (waiting in tx_wait_q list) in txq list.
@@ -218,6 +242,10 @@ static void ll_device_want_to_wakeup(struct hci_uart *hu)
BT_DBG("dual wake-up-indication");
/* deliberate fall-through - do not add break */
+ /* Make sure clock is on - we may have turned clock off since
+ * receiving the wake up indicator
+ */
+ __ll_msm_serial_clock_on(hu->tty);
/* acknowledge device wake up */
if (send_hcill_cmd(HCILL_WAKE_UP_ACK, hu) < 0) {
BT_ERR("cannot acknowledge device wake up");
@@ -271,6 +299,11 @@ out:

/* actually send the sleep ack packet */
+ spin_lock_irqsave(&ll->hcill_lock, flags);
+ if (ll->hcill_state == HCILL_ASLEEP)
+ __ll_msm_serial_clock_request_off(hu->tty);
+ spin_unlock_irqrestore(&ll->hcill_lock, flags);

@@ -322,6 +355,7 @@ static int ll_enqueue(struct hci_uart *hu, struct
sk_buff *skb)
BT_DBG("device asleep, waking up and queueing packet");
+ __ll_msm_serial_clock_on(hu->tty);
/* save packet for later */
skb_queue_tail(&ll->tx_wait_q, skb);
/* awake device */