Skip to content

Commit

Permalink
Bluetooth: btusb: Add out-of-band wakeup support
Browse files Browse the repository at this point in the history
Some onboard BT chips (e.g. Marvell 8997) contain a wakeup pin that
can be connected to a gpio on the CPU side, and can be used to wakeup
the host out-of-band. This can be useful in situations where the
in-band wakeup is not possible or not preferable (e.g. the in-band
wakeup may require the USB host controller to remain active, and
hence consuming more system power during system sleep).

The oob gpio interrupt to be used for wakeup on the CPU side, is
read from the device tree node, (using standard interrupt descriptors).
A devcie tree binding document is also added for the driver. The
compatible string is in compliance with
Documentation/devicetree/bindings/usb/usb-device.txt

Signed-off-by: Rajat Jain <rajatja@google.com>
Reviewed-by: Brian Norris <briannorris@chromium.org>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
  • Loading branch information
Rajat Jain authored and holtmann committed Feb 16, 2017
1 parent 10ab133 commit fd913ef
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
40 changes: 40 additions & 0 deletions Documentation/devicetree/bindings/net/btusb.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Generic Bluetooth controller over USB (btusb driver)
---------------------------------------------------

Required properties:

- compatible : should comply with the format "usbVID,PID" specified in
Documentation/devicetree/bindings/usb/usb-device.txt
At the time of writing, the only OF supported devices
(more may be added later) are:

"usb1286,204e" (Marvell 8997)

Optional properties:

- interrupt-parent: phandle of the parent interrupt controller
- interrupt-names: (see below)
- interrupts : The interrupt specified by the name "wakeup" is the interrupt
that shall be used for out-of-band wake-on-bt. Driver will
request this interrupt for wakeup. During system suspend, the
irq will be enabled so that the bluetooth chip can wakeup host
platform out of band. During system resume, the irq will be
disabled to make sure unnecessary interrupt is not received.

Example:

Following example uses irq pin number 3 of gpio0 for out of band wake-on-bt:

&usb_host1_ehci {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;

mvl_bt1: bt@1 {
compatible = "usb1286,204e";
reg = <1>;
interrupt-parent = <&gpio0>;
interrupt-name = "wakeup";
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
};
};
85 changes: 85 additions & 0 deletions drivers/bluetooth/btusb.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/firmware.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <asm/unaligned.h>

#include <net/bluetooth/bluetooth.h>
Expand Down Expand Up @@ -373,6 +375,7 @@ static const struct usb_device_id blacklist_table[] = {
#define BTUSB_BOOTING 9
#define BTUSB_RESET_RESUME 10
#define BTUSB_DIAG_RUNNING 11
#define BTUSB_OOB_WAKE_ENABLED 12

struct btusb_data {
struct hci_dev *hdev;
Expand Down Expand Up @@ -420,6 +423,8 @@ struct btusb_data {
int (*recv_bulk)(struct btusb_data *data, void *buffer, int count);

int (*setup_on_usb)(struct hci_dev *hdev);

int oob_wake_irq; /* irq for out-of-band wake-on-bt */
};

static inline void btusb_free_frags(struct btusb_data *data)
Expand Down Expand Up @@ -2732,6 +2737,66 @@ static int btusb_bcm_set_diag(struct hci_dev *hdev, bool enable)
}
#endif

#ifdef CONFIG_PM
static irqreturn_t btusb_oob_wake_handler(int irq, void *priv)
{
struct btusb_data *data = priv;

pm_wakeup_event(&data->udev->dev, 0);

/* Disable only if not already disabled (keep it balanced) */
if (test_and_clear_bit(BTUSB_OOB_WAKE_ENABLED, &data->flags)) {
disable_irq_nosync(irq);
disable_irq_wake(irq);
}
return IRQ_HANDLED;
}

static const struct of_device_id btusb_match_table[] = {
{ .compatible = "usb1286,204e" },
{ }
};
MODULE_DEVICE_TABLE(of, btusb_match_table);

/* Use an oob wakeup pin? */
static int btusb_config_oob_wake(struct hci_dev *hdev)
{
struct btusb_data *data = hci_get_drvdata(hdev);
struct device *dev = &data->udev->dev;
int irq, ret;

clear_bit(BTUSB_OOB_WAKE_ENABLED, &data->flags);

if (!of_match_device(btusb_match_table, dev))
return 0;

/* Move on if no IRQ specified */
irq = of_irq_get_byname(dev->of_node, "wakeup");
if (irq <= 0) {
bt_dev_dbg(hdev, "%s: no OOB Wakeup IRQ in DT", __func__);
return 0;
}

ret = devm_request_irq(&hdev->dev, irq, btusb_oob_wake_handler,
0, "OOB Wake-on-BT", data);
if (ret) {
bt_dev_err(hdev, "%s: IRQ request failed", __func__);
return ret;
}

ret = device_init_wakeup(dev, true);
if (ret) {
bt_dev_err(hdev, "%s: failed to init_wakeup", __func__);
return ret;
}

data->oob_wake_irq = irq;
disable_irq(irq);
bt_dev_info(hdev, "OOB Wake-on-BT configured at IRQ %u", irq);
return 0;
}
#endif

static int btusb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
Expand Down Expand Up @@ -2853,6 +2918,11 @@ static int btusb_probe(struct usb_interface *intf,
hdev->send = btusb_send_frame;
hdev->notify = btusb_notify;

#ifdef CONFIG_PM
err = btusb_config_oob_wake(hdev);
if (err)
goto out_free_dev;
#endif
if (id->driver_info & BTUSB_CW6622)
set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);

Expand Down Expand Up @@ -3065,6 +3135,9 @@ static void btusb_disconnect(struct usb_interface *intf)
usb_driver_release_interface(&btusb_driver, data->isoc);
}

if (data->oob_wake_irq)
device_init_wakeup(&data->udev->dev, false);

hci_free_dev(hdev);
}

Expand Down Expand Up @@ -3093,6 +3166,12 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message)
btusb_stop_traffic(data);
usb_kill_anchored_urbs(&data->tx_anchor);

if (data->oob_wake_irq && device_may_wakeup(&data->udev->dev)) {
set_bit(BTUSB_OOB_WAKE_ENABLED, &data->flags);
enable_irq_wake(data->oob_wake_irq);
enable_irq(data->oob_wake_irq);
}

/* Optionally request a device reset on resume, but only when
* wakeups are disabled. If wakeups are enabled we assume the
* device will stay powered up throughout suspend.
Expand Down Expand Up @@ -3130,6 +3209,12 @@ static int btusb_resume(struct usb_interface *intf)
if (--data->suspend_count)
return 0;

/* Disable only if not already disabled (keep it balanced) */
if (test_and_clear_bit(BTUSB_OOB_WAKE_ENABLED, &data->flags)) {
disable_irq(data->oob_wake_irq);
disable_irq_wake(data->oob_wake_irq);
}

if (!test_bit(HCI_RUNNING, &hdev->flags))
goto done;

Expand Down

0 comments on commit fd913ef

Please sign in to comment.