|
@@ -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>
|
|
@@ -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;
|
|
@@ -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)
|
|
@@ -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)
|
|
|
{
|
|
@@ -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);
|
|
|
|
|
@@ -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);
|
|
|
}
|
|
|
|
|
@@ -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.
|
|
@@ -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;
|
|
|
|