From 24d48e096b0a6f006c3a4423d249aae1592e8543 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Wed, 18 Mar 2026 15:59:07 +0800 Subject: [PATCH] WORKAROUND: Bluetooth: support Bluetooth over both USB and UART This is a temporary workaround. On Hamoa boards, a single M.2 slot may host either a UART-based Bluetooth card or a USB-based Bluetooth card. As a result, the UART controller node is always present in the device tree, while the USB Bluetooth path is hot-pluggable. When Bluetooth is actually used over USB, the hci_qca UART driver still probes due to the DT node being present. During probe and power sequencing, it drives BT_EN low (either directly or via the WCN power sequencer), which can cut power to the shared Bluetooth device and cause the USB Bluetooth interface to disconnect. Model BT_EN as an always-on fixed regulator and ensure that the UART path does not claim power control when BT_EN is not described for that consumer: - Describe BT_EN as a fixed regulator (GPIO 116) that is boot-on and always-on, so it cannot be inadvertently deasserted by the UART probe. - Remove bt-enable-gpios from the WCN7850 power sequencer node and drop its BT pinctrl entry. - Wire the Bluetooth DT supplies to the always-on 3.3V rail so the UART driver can acquire its regulators without depending on the WCN PMU regulators in this dual-path configuration. - Treat WCN7850 like WCN6750/WCN6855 in hci_qca: if no bt_en GPIO is provided for the UART device, disable power control for that instance. - In pwrseq-qcom-wcn, do not match a "bluetooth" consumer if the sequencer has no bt-enable GPIO configured. This prevents the sequencer from binding to a Bluetooth consumer node in the "BT_EN tied high / absent from DT" case and allows the consumer to fall back accordingly. With these changes, probing the UART path no longer deasserts BT_EN when Bluetooth is operating over USB, avoiding unexpected USB disconnects. Signed-off-by: Shuai Zhang --- arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 42 +++++++++++++++------- drivers/bluetooth/hci_qca.c | 3 +- drivers/power/sequencing/pwrseq-qcom-wcn.c | 14 ++++++++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts index 6603fd262e927..35113535946e0 100644 --- a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts +++ b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts @@ -470,6 +470,23 @@ vin-supply = <&vreg_wcn_3p3>; }; + vreg_wcn_bt_en: regulator-wcn-bt-en { + compatible = "regulator-fixed"; + + regulator-name = "VREG_WCN_BT_EN"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + gpio = <&tlmm 116 GPIO_ACTIVE_HIGH>; + enable-active-high; + + pinctrl-0 = <&wcn_bt_en>; + pinctrl-names = "default"; + + regulator-always-on; + regulator-boot-on; + }; + vreg_wcn_3p3: regulator-wcn-3p3 { compatible = "regulator-fixed"; @@ -1330,12 +1347,11 @@ output-low; }; - wcn_bt_en_hog: wcn-bt-en-state-hog { - gpio-hog; - gpios = <116 GPIO_ACTIVE_HIGH>; - output-high; - input-disable; - line-name = "BT_EN"; + wcn_bt_en: wcn-bt-en-state { + pins = "gpio116"; + function = "gpio"; + drive-strength = <2>; + bias-disable; }; wcn_wlan_en: wcn-wlan-en-state { @@ -1376,13 +1392,13 @@ compatible = "qcom,wcn7850-bt"; max-speed = <3200000>; - vddaon-supply = <&vreg_pmu_aon_0p59>; - vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; - vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; - vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; - vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; - vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; - vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>; + vddrfacmn-supply = <&vreg_wcn_3p3>; + vddaon-supply = <&vreg_wcn_3p3>; + vddwlcx-supply = <&vreg_wcn_3p3>; + vddwlmx-supply = <&vreg_wcn_3p3>; + vddrfa0p8-supply = <&vreg_wcn_3p3>; + vddrfa1p2-supply = <&vreg_wcn_3p3>; + vddrfa1p8-supply = <&vreg_wcn_3p3>; }; }; diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index c0cc04995fc2f..56663f6717311 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2467,7 +2467,8 @@ static int qca_serdev_probe(struct serdev_device *serdev) if (!qcadev->bt_en && (data->soc_type == QCA_WCN6750 || - data->soc_type == QCA_WCN6855)) + data->soc_type == QCA_WCN6855 || + data->soc_type == QCA_WCN7850)) power_ctrl_enabled = false; qcadev->sw_ctrl = devm_gpiod_get_optional(&serdev->dev, "swctrl", diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index 663d9a5370653..916fa056025f5 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -357,6 +357,20 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq, reg_node->parent->parent != ctx->of_node) return PWRSEQ_NO_MATCH; + /* + * If this is a Bluetooth consumer device but the bt-enable GPIO is not + * configured in the power sequencer (e.g. BT_EN is tied high via a + * hardware pull-up and therefore absent from the DT), don't match. + * The consumer driver will fall back to its legacy power control path + * and correctly set power_ctrl_enabled to false. + * + * BT device nodes are conventionally named "bluetooth" in the DT, + * so use of_node_name_eq() as a generic check rather than enumerating + * specific compatible strings. + */ + if (!ctx->bt_gpio && of_node_name_eq(dev_node, "bluetooth")) + return PWRSEQ_NO_MATCH; + return PWRSEQ_MATCH_OK; }