Re: [PATCH v2 3/4] Bluetooth: hci_qca: Support QCA2066 on M.2 connector via pwrseq

From: Dmitry Baryshkov

Date: Thu Jul 02 2026 - 08:37:50 EST


On Thu, Jul 02, 2026 at 12:46:15PM +0200, Loic Poulain wrote:
> 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.
>
> Also allocate bt_power unconditionally for all SOC types since the

Can we just fold it into the main struct?

> pwrseq path is independent of the SOC type switch.
>
> Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
> ---
> drivers/bluetooth/hci_qca.c | 81 ++++++++++++++++++++++++---------------------
> 1 file changed, 43 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
> index e09debdb00a1b8e74ccd5de6147e240e533b4594..b04593a96e14ac9e87ae76fa00eda308e81dea25 100644
> --- a/drivers/bluetooth/hci_qca.c
> +++ b/drivers/bluetooth/hci_qca.c
> @@ -1872,6 +1872,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);
> @@ -2256,7 +2259,7 @@ static void qca_power_off(struct hci_uart *hu)
> break;
> }
>
> - if (power && power->pwrseq) {
> + if (power->pwrseq) {
> pwrseq_power_off(power->pwrseq);
> set_bit(QCA_BT_OFF, &qca->flags);
> return;
> @@ -2387,6 +2390,35 @@ static int qca_init_regulators(struct qca_power *qca,
> return 0;
> }
>
> +/*
> + * Acquire the M.2 connector power sequencer.
> + *
> + * An OF graph link on the serdev controller is only present when the BT
> + * device is attached through an M.2 Key E connector. In that case the UART
> + * enable (W_DISABLE2#) is driven by the pcie-m2 power sequencer instead of a
> + * dedicated BT enable GPIO, so grab the "uart" pwrseq target from it.
> + *
> + * Returns 0 if no M.2 connector is present (nothing to do), a negative errno
> + * on error, otherwise 0 with qcadev->bt_power->pwrseq populated.
> + */
> +static int qca_serdev_get_m2_pwrseq(struct qca_serdev *qcadev, bool *bt_en_available)
> +{
> + struct serdev_device *serdev = qcadev->serdev_hu.serdev;
> + struct device *dev;
> +
> + if (!of_graph_is_present(dev_of_node(&serdev->ctrl->dev)))
> + return 0;
> +
> + 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);
> +
> + dev = pwrseq_to_device(qcadev->bt_power->pwrseq);
> + *bt_en_available = device_property_present(dev, "w-disable2-gpios");

I think here you are looking into the exact details of the other of the
graph. There might be other devices on that side, while the code now
assumes M.2. Or, consider having an M.2 controller which handles
W_DISABLE2# internally rather than through the GPIO.

> +
> + return 0;
> +}
> +
> static int qca_serdev_probe(struct serdev_device *serdev)
> {
> struct qca_serdev *qcadev;

--
With best wishes
Dmitry