From: Nikolay Bryskin <nbryskin@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: Nikolay Bryskin <nbryskin@gmail.com>,
Bartosz Golaszewski <brgl@kernel.org>,
Marcel Holtmann <marcel@holtmann.org>,
Luiz Augusto von Dentz <luiz.dentz@gmail.com>,
Rocky Liao <quic_rjliao@quicinc.com>,
linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] Bluetooth: hci_qca: Reprobe QCA6390 after hibernation
Date: Sat, 17 Oct 2026 20:43:10 +0300 [thread overview]
Message-ID: <20261017174310.22484-1-nbryskin@gmail.com> (raw)
Without a way to power-cycle the SoC (no BT_EN GPIO and no power
sequencer, e.g. the ACPI-enumerated QCA6390 on the Dell XPS 13 9310),
HCI_QUIRK_NON_PERSISTENT_SETUP is not set and hdev->setup() runs only
once, at probe, on the assumption that the SoC keeps its firmware.
Hibernation breaks that assumption: the platform powers the SoC off,
and after the image is restored it is back in its ROM state. The
driver's resume callback, shared with suspend-to-idle via
SIMPLE_DEV_PM_OPS(), only clears QCA_SUSPENDING, so the first command
times out and the controller stays unusable. Error recovery and
power-cycling the adapter from userspace don't help either, because
none of them run setup again:
Bluetooth: hci0: Opcode 0x0c01 failed: -110
Bluetooth: hci0: command 0x0c01 tx timeout
Bluetooth: hci0: crash the soc to collect controller dump
Bluetooth: hci0: Opcode 0x0c05 failed: -110
Bluetooth: hci0: Frame reassembly failed (-84)
...
Bluetooth: hci0: Opcode 0x0c03 failed: -110
Give the restore phase its own callback which, in this configuration,
rebinds the driver so that probe downloads the firmware again. The
rebind is done from a work item on system_freezable_wq, as it can't
run from within a PM callback and it frees the qca_serdev the callback
works on. All other sleep transitions keep their existing callbacks.
Rebinding works here because the SoC is in its ROM state after the
restore. Rebinding while it still runs the downloaded firmware fails
the same way a warm reboot used to before commit 7e7bbddd029b
("Bluetooth: hci_qca: Fix qca6390 enable failure after warm reboot"),
which this patch doesn't change.
Tested on a Dell XPS 13 9310 (DLB16390): Bluetooth comes back about a
second after resuming from hibernation, with no command timeouts.
Suspend-to-idle is unaffected.
Fixes: e5d6468fe9d8 ("Bluetooth: hci_qca: Add support for Qualcomm Bluetooth SoC QCA6390")
Assisted-by: LLM
Signed-off-by: Nikolay Bryskin <nbryskin@gmail.com>
---
drivers/bluetooth/hci_qca.c | 58 ++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
index 7089e9b639b2..7bca14d364b9 100644
--- a/drivers/bluetooth/hci_qca.c
+++ b/drivers/bluetooth/hci_qca.c
@@ -2775,7 +2775,63 @@ static int __maybe_unused qca_resume(struct device *dev)
return 0;
}
-static SIMPLE_DEV_PM_OPS(qca_pm_ops, qca_suspend, qca_resume);
+struct qca_reprobe {
+ struct work_struct work;
+ struct device *dev;
+};
+
+static void qca_reprobe_work(struct work_struct *work)
+{
+ struct qca_reprobe *reprobe = container_of(work, struct qca_reprobe,
+ work);
+ int ret;
+
+ ret = device_reprobe(reprobe->dev);
+ if (ret && ret != -EPROBE_DEFER)
+ dev_err(reprobe->dev, "Failed to reprobe device (error %d)\n",
+ ret);
+
+ put_device(reprobe->dev);
+ kfree(reprobe);
+}
+
+static int __maybe_unused qca_restore(struct device *dev)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct qca_serdev *qcadev = serdev_device_get_drvdata(serdev);
+ struct hci_uart *hu = &qcadev->serdev_hu;
+ struct qca_reprobe *reprobe;
+
+ qca_resume(dev);
+
+ /* Without power control hdev->setup() runs only once, at probe, as
+ * the SoC is expected to keep its firmware. Hibernation powers the
+ * SoC off, so rebind the driver to download the firmware again. The
+ * rebind can't run from a PM callback, and qcadev is gone once it
+ * does, so it gets a work item of its own.
+ */
+ if (hci_test_quirk(hu->hdev, HCI_QUIRK_NON_PERSISTENT_SETUP))
+ return 0;
+
+ reprobe = kzalloc_obj(*reprobe);
+ if (!reprobe)
+ return -ENOMEM;
+
+ INIT_WORK(&reprobe->work, qca_reprobe_work);
+ reprobe->dev = get_device(dev);
+ queue_work(system_freezable_wq, &reprobe->work);
+
+ return 0;
+}
+
+static const struct dev_pm_ops qca_pm_ops = {
+ .suspend = pm_sleep_ptr(qca_suspend),
+ .resume = pm_sleep_ptr(qca_resume),
+ .freeze = pm_sleep_ptr(qca_suspend),
+ .thaw = pm_sleep_ptr(qca_resume),
+ .poweroff = pm_sleep_ptr(qca_suspend),
+ .restore = pm_sleep_ptr(qca_restore),
+};
#ifdef CONFIG_OF
static const struct of_device_id qca_bluetooth_of_match[] = {
--
2.56.0
next reply other threads:[~2026-10-17 17:43 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 17:43 Nikolay Bryskin [this message]
2026-10-17 19:30 ` Bluetooth: hci_qca: Reprobe QCA6390 after hibernation bluez.test.bot
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=20261017174310.22484-1-nbryskin@gmail.com \
--to=nbryskin@gmail.com \
--cc=brgl@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=quic_rjliao@quicinc.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox