@@ -238,6 +238,13 @@ enum {
* during the hdev->setup vendor callback.
*/
HCI_QUIRK_BROKEN_ERR_DATA_REPORTING,
+
+ /* When this quirk is set, the adapter will be powered down during
+ * system suspend and powerd up on resume. This should be used on
+ * controllers that don't behave well during suspend, either causing
+ * spurious wakeups or not entering a suspend state reliably.
+ */
+ HCI_QUIRK_POWER_DOWN_SYSTEM_SUSPEND,
};
/* HCI device flags */
@@ -90,6 +90,7 @@ struct discovery_state {
};
#define SUSPEND_NOTIFIER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */
+#define SUSPEND_POWER_DOWN_TIMEOUT msecs_to_jiffies(1000)
enum suspend_tasks {
SUSPEND_PAUSE_DISCOVERY,
@@ -112,6 +113,9 @@ enum suspended_state {
BT_RUNNING = 0,
BT_SUSPEND_DISCONNECT,
BT_SUSPEND_CONFIGURE_WAKE,
+ BT_SUSPEND_DO_POWER_DOWN,
+ BT_SUSPEND_DO_POWER_UP,
+ BT_SUSPEND_POWERED_DOWN, /* Powered down prior to suspend */
};
struct hci_conn_hash {
@@ -3562,6 +3562,7 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
container_of(nb, struct hci_dev, suspend_notifier);
int ret = 0;
u8 state = BT_RUNNING;
+ bool powered;
/* If powering down, wait for completion. */
if (mgmt_powering_down(hdev)) {
@@ -3571,8 +3572,51 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
goto done;
}
- /* Suspend notifier should only act on events when powered. */
- if (!hdev_is_powered(hdev))
+ powered = hdev_is_powered(hdev);
+
+ /* Update the suspend state when entering suspend if the system is
+ * currently powered off or if it is powered on but was previously
+ * powered off.
+ */
+ if (action == PM_SUSPEND_PREPARE) {
+ /* Must hold dev lock when modifying suspend state. */
+ hci_dev_lock(hdev);
+ if (powered && hdev->suspend_state == BT_SUSPEND_POWERED_DOWN)
+ hdev->suspend_state = BT_RUNNING;
+ else if (!powered &&
+ hdev->suspend_state != BT_SUSPEND_POWERED_DOWN)
+ hdev->suspend_state = BT_SUSPEND_POWERED_DOWN;
+
+ hci_dev_unlock(hdev);
+ }
+
+ /* When the power down quirk is set, we power down the adapter when
+ * suspending and power it up when resuming. If the adapter was already
+ * powered down before suspend, we don't do anything here.
+ */
+ if (test_bit(HCI_QUIRK_POWER_DOWN_SYSTEM_SUSPEND, &hdev->quirks) &&
+ hdev->suspend_state != BT_SUSPEND_POWERED_DOWN) {
+ if (action == PM_SUSPEND_PREPARE && powered) {
+ state = BT_SUSPEND_DO_POWER_DOWN;
+ ret = hci_change_suspend_state(hdev, state);
+
+ /* Emit that we're powering down for suspend */
+ hci_clear_wake_reason(hdev);
+ mgmt_suspending(hdev, state);
+ goto done;
+ } else if (action == PM_POST_SUSPEND && !powered) {
+ /* Emit that we're resuming before powering up. */
+ mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr,
+ hdev->wake_addr_type);
+
+ state = BT_SUSPEND_DO_POWER_UP;
+ ret = hci_change_suspend_state(hdev, state);
+ goto done;
+ }
+ }
+
+ /* Skip to end if we weren't powered. */
+ if (!powered)
goto done;
if (action == PM_SUSPEND_PREPARE) {
@@ -1194,6 +1194,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
struct hci_request req;
u8 page_scan;
int disconnect_counter;
+ int err;
if (next == hdev->suspend_state) {
bt_dev_dbg(hdev, "Same state before and after: %d", next);
@@ -1273,7 +1274,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
/* Pause scan changes again. */
hdev->scanning_paused = true;
hci_req_run(&req, suspend_req_complete);
- } else {
+ } else if (next == BT_RUNNING) {
hdev->suspended = false;
hdev->scanning_paused = false;
@@ -1306,6 +1307,29 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
}
hci_req_run(&req, suspend_req_complete);
+ } else if (next == BT_SUSPEND_DO_POWER_DOWN) {
+ hdev->suspended = true;
+ hdev->scanning_paused = true;
+
+ err = hci_clean_up_state(hdev);
+
+ if (!err)
+ queue_delayed_work(hdev->req_workqueue,
+ &hdev->power_off,
+ SUSPEND_POWER_DOWN_TIMEOUT);
+
+ if (err == -ENODATA) {
+ cancel_delayed_work(&hdev->power_off);
+ queue_work(hdev->req_workqueue, &hdev->power_off.work);
+ err = 0;
+ }
+
+ set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
+ } else if (next == BT_SUSPEND_DO_POWER_UP) {
+ hdev->suspended = false;
+ hdev->scanning_paused = false;
+
+ queue_work(hdev->req_workqueue, &hdev->power_on);
}
hdev->suspend_state = next;