[PATCH v6 08/11] Bluetooth: hci_qca: Support QCA2066 on M.2 connector via pwrseq
From: Loic Poulain
Date: Fri Sep 04 2026 - 13:36:29 EST
For QCA2066 (and other QCA chips) on M.2 connectors, the UART enable is
controlled by the W_DISABLE2# signal managed by the pcie-m2 power sequencer
rather than a dedicated BT enable GPIO.
When the serdev controller has an OF graph (indicating it is connected to
an M.2 connector), acquire the 'uart' pwrseq target from the connector's
power sequencer and use it to control BT power instead of the bt-enable
GPIO. This is factored out into qca_serdev_get_m2_pwrseq().
Reviewed-by: Manivannan Sadhasivam <mani@xxxxxxxxxx>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxxxxxxxx>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
---
drivers/bluetooth/hci_qca.c | 47 +++++++++++++++++++++++++++++++++++----------
1 file changed, 37 insertions(+), 10 deletions(-)
diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
index 30586120c4a73a50a29af235b511c3ae97960e27..db0a968dfb00fa6f1abb86a3f6d1be89dfe80c6a 100644
--- a/drivers/bluetooth/hci_qca.c
+++ b/drivers/bluetooth/hci_qca.c
@@ -1875,6 +1875,9 @@ static int qca_power_on(struct hci_dev *hdev)
/* Controller needs time to bootup. */
msleep(150);
}
+
+ if (qcadev->bt_power.pwrseq)
+ pwrseq_power_on(qcadev->bt_power.pwrseq);
}
clear_bit(QCA_BT_OFF, &qca->flags);
@@ -2390,6 +2393,34 @@ static int qca_init_regulators(struct qca_power *qca,
return 0;
}
+static void qca_serdev_put_pwrseq(void *data)
+{
+ pwrseq_put(data);
+}
+
+static int qca_serdev_get_m2_pwrseq(struct qca_serdev *qcadev)
+{
+ struct serdev_device *serdev = qcadev->serdev_hu.serdev;
+ struct pwrseq_desc *pwrseq;
+
+ if (!of_graph_is_present(dev_of_node(&serdev->ctrl->dev)))
+ return 0;
+
+ /* The pwrseq is looked up on the serdev controller (which holds the
+ * OF graph to the M.2 connector), but its lifetime must follow this
+ * serdev consumer device, not the controller. So acquire it with the
+ * non-devres pwrseq_get() and release it via a devres action bound to
+ * &serdev->dev instead of using devm_pwrseq_get(&serdev->ctrl->dev).
+ */
+ pwrseq = pwrseq_get(&serdev->ctrl->dev, "uart");
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ qcadev->bt_power.pwrseq = pwrseq;
+
+ return devm_add_action_or_reset(&serdev->dev, qca_serdev_put_pwrseq, pwrseq);
+}
+
static int qca_serdev_probe(struct serdev_device *serdev)
{
struct qca_serdev *qcadev;
@@ -2420,6 +2451,10 @@ static int qca_serdev_probe(struct serdev_device *serdev)
else
qcadev->btsoc_type = QCA_ROME;
+ err = qca_serdev_get_m2_pwrseq(qcadev);
+ if (err)
+ return err;
+
switch (qcadev->btsoc_type) {
case QCA_WCN3950:
case QCA_WCN3988:
@@ -2429,17 +2464,9 @@ static int qca_serdev_probe(struct serdev_device *serdev)
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
- /*
- * OF graph link is only present for BT devices attached through
- * the M.2 Key E connector.
- */
- if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) {
- qcadev->bt_power.pwrseq = devm_pwrseq_get(&serdev->ctrl->dev,
- "uart");
- if (IS_ERR(qcadev->bt_power.pwrseq))
- return PTR_ERR(qcadev->bt_power.pwrseq);
+ /* M.2 connector modules are powered by the pwrseq acquired above. */
+ if (qcadev->bt_power.pwrseq)
break;
- }
if (!device_property_present(&serdev->dev, "enable-gpios")) {
/*
--
2.34.1