From e71e4432642e89a47f469068954cbd1aebeef4e0 Mon Sep 17 00:00:00 2001 From: Vivek Sahu Date: Sat, 1 Aug 2026 20:10:00 +0530 Subject: [PATCH 1/4] BACKPORT: Bluetooth: qca: update QCC2072 NVM handling QCC2072 uses the ORN firmware and NVM naming scheme. The RB3 Gen 2 Industrial BT-over-UART setup also needs the BCS calibration TLV to be combined with the selected NVM before download. Keep the BCS/NVM combination in a helper so missing calibration data or allocation failures can fall back to downloading the NVM alone without a local skip label. Select the NVM file and BCS calibration file using the controller board ID when available, with fallback to the default files. Initialize the BCS calibration filename independently of the NVM filename source so custom NVM firmware-name paths do not leave it unset. [qcom-6.18.y: hci_qca QCC2072 registration from the upstream patch is already present, so keep only the btqca NVM and calibration update.] Signed-off-by: Vivek Sahu Co-developed-by: Yepuri Siddu Signed-off-by: Yepuri Siddu Signed-off-by: Rahul Samana Link: https://lore.kernel.org/r/20260727-rb3-industrial-bt-uart-v2-2-2d100f30e202@oss.qualcomm.com --- drivers/bluetooth/btqca.c | 112 +++++++++++++++++++++----------------- drivers/bluetooth/btqca.h | 1 + 2 files changed, 64 insertions(+), 49 deletions(-) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index 95394586a84b6..1a9d030517d3f 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -567,6 +567,54 @@ static int qca_inject_cmd_complete_event(struct hci_dev *hdev) return hci_recv_frame(hdev, skb); } +static void qca_combine_nvm_calib(struct hci_dev *hdev, u8 **data, + int *size, char *calib_name, + size_t max_size) +{ + const struct firmware *calib_fw; + struct tlv_type_hdr *outer_hdr; + size_t inner_len, combined_size; + u8 *combined_data; + int err; + + err = request_firmware(&calib_fw, calib_name, &hdev->dev); + if (err) { + if (qca_get_alt_nvm_file(calib_name, max_size)) + err = request_firmware(&calib_fw, calib_name, &hdev->dev); + + if (err) { + bt_dev_err(hdev, "QCA Failed to request file: %s (%d)", + calib_name, err); + return; + } + } + + bt_dev_info(hdev, "QCA Downloading %s", calib_name); + + inner_len = *size + calib_fw->size; + combined_size = sizeof(*outer_hdr) + inner_len; + combined_data = vmalloc(combined_size); + if (!combined_data) { + bt_dev_warn(hdev, + "QCA Failed to allocate memory for file: %s", + calib_name); + release_firmware(calib_fw); + return; + } + + outer_hdr = (struct tlv_type_hdr *)combined_data; + /* high 24 bits = payload length, low 8 bits = type */ + outer_hdr->type_len = cpu_to_le32((inner_len << 8) | 4); + memcpy(combined_data + sizeof(*outer_hdr), *data, *size); + memcpy(combined_data + sizeof(*outer_hdr) + *size, + calib_fw->data, calib_fw->size); + release_firmware(calib_fw); + + vfree(*data); + *data = combined_data; + *size = combined_size; +} + static int qca_download_firmware(struct hci_dev *hdev, struct qca_fw_config *config, enum qca_btsoc_type soc_type, @@ -612,52 +660,10 @@ static int qca_download_firmware(struct hci_dev *hdev, memcpy(data, fw->data, size); release_firmware(fw); - /* For QCC2072, combine the NVM (type 2) with the calibration file - * into a single TLV of outer type 4. - */ - if (soc_type == QCA_QCC2072 && config->type == TLV_TYPE_NVM) { - const struct firmware *calib_fw = NULL; - char calib_name[32]; - u8 *combined_data = NULL; - size_t inner_len, combined_size; - struct tlv_type_hdr *outer_hdr; - int err; - - snprintf(calib_name, sizeof(calib_name), - "qca/ornbcscal%02x.bin", rom_ver); - err = request_firmware(&calib_fw, calib_name, &hdev->dev); - if (err) { - bt_dev_err(hdev, "QCA Failed to request file: %s (%d)", - calib_name, err); - goto skip_combination; - } - - bt_dev_info(hdev, "QCA Downloading %s", calib_name); - - inner_len = size + calib_fw->size; - combined_size = sizeof(*outer_hdr) + inner_len; - combined_data = vmalloc(combined_size); - if (!combined_data) { - bt_dev_warn(hdev, - "QCA Failed to allocate memory for file: %s", - calib_name); - release_firmware(calib_fw); - goto skip_combination; - } - - outer_hdr = (struct tlv_type_hdr *)combined_data; - /* high 24 bits = payload length, low 8 bits = type */ - outer_hdr->type_len = cpu_to_le32((inner_len << 8) | 4); - memcpy(combined_data + sizeof(*outer_hdr), data, size); - memcpy(combined_data + sizeof(*outer_hdr) + size, - calib_fw->data, calib_fw->size); - release_firmware(calib_fw); - vfree(data); - data = combined_data; - size = combined_size; -skip_combination: - ; - } + if (soc_type == QCA_QCC2072 && config->type == TLV_TYPE_NVM) + qca_combine_nvm_calib(hdev, &data, &size, + config->calib_name, + sizeof(config->calib_name)); ret = qca_tlv_check_data(hdev, config, data, size, soc_type); if (ret) @@ -927,7 +933,8 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, /* Give the controller some time to get ready to receive the NVM */ msleep(10); - if (soc_type == QCA_QCA2066 || soc_type == QCA_WCN7850) + if (soc_type == QCA_QCA2066 || soc_type == QCA_WCN7850 || + soc_type == QCA_QCC2072) qca_read_fw_board_id(hdev, &boardid); /* Download NVM configuration */ @@ -988,8 +995,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, "hmtnv", soc_type, ver, rom_ver, boardid); break; case QCA_QCC2072: - snprintf(config.fwname, sizeof(config.fwname), - "qca/ornnv%02x.bin", rom_ver); + qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname), + "ornnv", soc_type, ver, + rom_ver, boardid); break; default: snprintf(config.fwname, sizeof(config.fwname), @@ -997,6 +1005,12 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, } } + if (soc_type == QCA_QCC2072) + qca_get_nvm_name_by_board(config.calib_name, + sizeof(config.calib_name), + "ornbcscal", soc_type, ver, + rom_ver, boardid); + err = qca_download_firmware(hdev, &config, soc_type, rom_ver); if (err < 0 && !firmware_name && soc_type == QCA_WCN6855) { qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname), diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h index a175ac31e7b23..425133096eda5 100644 --- a/drivers/bluetooth/btqca.h +++ b/drivers/bluetooth/btqca.h @@ -94,6 +94,7 @@ enum qca_tlv_type { struct qca_fw_config { u8 type; char fwname[64]; + char calib_name[64]; uint8_t user_baud_rate; enum qca_tlv_dnld_mode dnld_mode; enum qca_tlv_dnld_mode dnld_type; From d04e4fdd5304a7aaa58b7c1bda18f0d11c137ee7 Mon Sep 17 00:00:00 2001 From: Rahul Samana Date: Sat, 1 Aug 2026 20:11:00 +0530 Subject: [PATCH 2/4] BACKPORT: arm64: dts: qcom: qcs6490-rb3gen2: label BT PMU node The reworked RB3 Gen 2 Industrial BT UART overlay needs to disable the on-board WCN6750 PMU. Label the exact WCN6750 PMU node so the overlay can patch it directly. [qcom-6.18.y: keep only the BT PMU label from the upstream-posted label patch; the M.2 PCIe graph endpoint is modelled from the overlay using the nested PCIe port path.] Signed-off-by: Rahul Samana Link: https://lore.kernel.org/r/20260727-rb3-industrial-bt-uart-v2-4-2d100f30e202@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts index ef4055f3b364a..6d591c6c7956e 100644 --- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts +++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts @@ -349,7 +349,7 @@ }; }; - wcn6750-pmu { + wcn6750_pmu: wcn6750-pmu { compatible = "qcom,wcn6750-pmu"; pinctrl-0 = <&bt_en>; pinctrl-names = "default"; From 9fe06772aa1218deabff40b46cf44c92f4fede76 Mon Sep 17 00:00:00 2001 From: Rahul Samana Date: Sat, 1 Aug 2026 20:12:00 +0530 Subject: [PATCH 3/4] BACKPORT: arm64: dts: qcom: sc7280: mark PCIe root port as bridge The PCI core needs to associate the DT node below the QCS6490 PCIe host bridge with the enumerated PCI-to-PCI bridge device before child nodes can be matched against the PCI topology. Add compatible = "pciclass,0604" to pcie0_port so child nodes below the bridge can be matched by the PCI device class. [qcom-6.18.y: upstream applies this change to kodiak.dtsi, while this branch still carries the PCIe root port node in sc7280.dtsi.] Reviewed-by: Konrad Dybcio Signed-off-by: Rahul Samana Link: https://lore.kernel.org/r/20260727-rb3-industrial-bt-uart-v2-5-2d100f30e202@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/sc7280.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/qcom/sc7280.dtsi b/arch/arm64/boot/dts/qcom/sc7280.dtsi index d713ace388067..b017b80218f2b 100644 --- a/arch/arm64/boot/dts/qcom/sc7280.dtsi +++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi @@ -2333,6 +2333,7 @@ status = "disabled"; pcie0_port: pcie@0 { + compatible = "pciclass,0604"; device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; bus-range = <0x01 0xff>; From a235b6a53b2861d3535eeb6414f5d2f246f63be1 Mon Sep 17 00:00:00 2001 From: Rahul Samana Date: Sat, 1 Aug 2026 20:13:00 +0530 Subject: [PATCH 4/4] BACKPORT: arm64: dts: qcom: rb3gen2: add Industrial BT UART overlay The reworked RB3 Gen 2 Industrial mezzanine keeps the common Industrial mezzanine hardware description but routes QCC2072 Bluetooth over UART4 instead of the default Bluetooth-over-USB path. Build this variant by applying the common Industrial mezzanine overlay first, followed by the BT UART overlay. The overlay models the M.2 E-key connector graph endpoints for PCIe and UART, and disables the on-board WCN6750 PMU and UART7 path so the M.2 QCC2072 Bluetooth controller can be used instead. [qcom-6.18.y: replace the old downstream m2-cologne overlay target and file with the generic BT UART overlay name used by the upstream-posted series.] Signed-off-by: Rahul Samana Link: https://lore.kernel.org/r/20260727-rb3-industrial-bt-uart-v2-6-2d100f30e202@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/Makefile | 4 +- ...-rb3gen2-industrial-mezzanine-bt-uart.dtso | 133 ++++++++++++++++++ ...3gen2-industrial-mezzanine-m2-cologne.dtso | 110 --------------- 3 files changed, 135 insertions(+), 112 deletions(-) create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtso delete mode 100644 arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile index f06c873fce0e8..9056576bdf7bc 100644 --- a/arch/arm64/boot/dts/qcom/Makefile +++ b/arch/arm64/boot/dts/qcom/Makefile @@ -191,14 +191,14 @@ dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-el2.dtb qcs6490-rb3gen2-vision-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine.dtbo qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo -qcs6490-rb3gen2-industrial-mezzanine-m2-cologne-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtbo +qcs6490-rb3gen2-industrial-mezzanine-bt-uart-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine.dtb qcs6490-rb3gen2-industrial-mezzanine-el2-dtbs := qcs6490-rb3gen2-industrial-mezzanine.dtb kodiak-el2.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine-el2.dtb +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtb dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine.dtb -dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtb qcs6490-rb3gen2-vision-mezzanine-el2-dtbs := qcs6490-rb3gen2-vision-mezzanine.dtb kodiak-el2.dtbo dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine-el2.dtb diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtso b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtso new file mode 100644 index 0000000000000..591af9dfc64e1 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-bt-uart.dtso @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/dts-v1/; +/plugin/; + +#include +#include + +&{/} { + vreg_wcn_3p3: regulator-wcn-3p3 { + compatible = "regulator-fixed"; + regulator-name = "VREG_WCN_3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + + regulator-boot-on; + }; + + m2-e-connector { + compatible = "pcie-m2-e-connector"; + vpcie3v3-supply = <&vreg_wcn_3p3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + m2_e_pcie_ep: endpoint@0 { + reg = <0>; + remote-endpoint = <&pcie0_m2_e_ep>; + }; + }; + + port@3 { + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + + m2_e_uart_ep: endpoint@0 { + reg = <0>; + remote-endpoint = <&uart4_m2_e_ep>; + }; + }; + }; + }; +}; + +&qup_uart4_cts { + bias-bus-hold; +}; + +&qup_uart4_rts { + drive-strength = <2>; + bias-disable; +}; + +&qup_uart4_rx { + bias-pull-up; +}; + +&qup_uart4_tx { + drive-strength = <2>; + bias-disable; +}; + +&tlmm { + qup_uart4_sleep_cts: qup-uart4-sleep-cts-state { + pins = "gpio16"; + function = "gpio"; + bias-bus-hold; + }; + + qup_uart4_sleep_rts: qup-uart4-sleep-rts-state { + pins = "gpio17"; + function = "gpio"; + bias-pull-down; + }; + + qup_uart4_sleep_tx: qup-uart4-sleep-tx-state { + pins = "gpio18"; + function = "gpio"; + bias-pull-up; + }; + + qup_uart4_sleep_rx: qup-uart4-sleep-rx-state { + pins = "gpio19"; + function = "gpio"; + bias-pull-up; + }; +}; + +&pcie0_port { + pcie@0,0 { + pcie@2,0 { + port { + pcie0_m2_e_ep: endpoint { + remote-endpoint = <&m2_e_pcie_ep>; + }; + }; + }; + }; +}; + +&uart4 { + interrupts-extended = <&intc GIC_SPI 605 IRQ_TYPE_LEVEL_HIGH>, + <&tlmm 19 IRQ_TYPE_EDGE_FALLING>; + pinctrl-names = "default", "sleep"; + pinctrl-1 = <&qup_uart4_sleep_cts>, <&qup_uart4_sleep_rts>, + <&qup_uart4_sleep_tx>, <&qup_uart4_sleep_rx>; + + status = "okay"; + + port { + uart4_m2_e_ep: endpoint { + remote-endpoint = <&m2_e_uart_ep>; + }; + }; +}; + +&uart7 { + status = "disabled"; +}; + +&wcn6750_pmu { + status = "disabled"; +}; diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso deleted file mode 100644 index 905a40f4b938f..0000000000000 --- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2-industrial-mezzanine-m2-cologne.dtso +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -/* - * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. -*/ - -/dts-v1/; -/plugin/; -#include - -&tlmm { - qup_uart4_sleep_cts: qup-uart4-sleep-cts-state { - pins = "gpio16"; - function = "gpio"; - /* - * Configure a bias-bus-hold on CTS to lower power - * usage when Bluetooth is turned off. Bus hold will - * maintain a low power state regardless of whether - * the Bluetooth module drives the pin in either - * direction or leaves the pin fully unpowered. - */ - bias-bus-hold; - }; - - qup_uart4_sleep_rts: qup-uart4-sleep-rts-state { - pins = "gpio17"; - function = "gpio"; - /* - * Configure pull-down on RTS. As RTS is active low - * signal, pull it low to indicate the BT SoC that it - * can wakeup the system anytime from suspend state by - * pulling RX low (by sending wakeup bytes). - */ - bias-pull-down; - }; - - qup_uart4_sleep_rx: qup-uart4-sleep-rx-state { - pins = "gpio19"; - function = "gpio"; - /* - * Configure a pull-up on RX. This is needed to avoid - * garbage data when the TX pin of the Bluetooth module - * is floating which may cause spurious wakeups. - */ - bias-pull-up; - }; - - qup_uart4_sleep_tx: qup-uart4-sleep-tx-state { - pins = "gpio18"; - function = "gpio"; - /* - * Configure pull-up on TX when it isn't actively driven - * to prevent BT SoC from receiving garbage during sleep. - */ - bias-pull-up; - }; - - sw_ctrl: sw-ctrl-state { - pins = "gpio86"; - function = "gpio"; - bias-pull-down; - }; -}; - -&qup_uart4_cts { - /* - * Configure a bias-bus-hold on CTS to lower power - * usage when Bluetooth is turned off. Bus hold will - * maintain a low power state regardless of whether - * the Bluetooth module drives the pin in either - * direction or leaves the pin fully unpowered. - */ - bias-bus-hold; -}; - -&qup_uart4_rts { - /* We'll drive RTS, so no pull */ - drive-strength = <2>; - bias-disable; -}; - -&qup_uart4_rx { - /* - * Configure a pull-up on RX. This is needed to avoid - * garbage data when the TX pin of the Bluetooth module is - * in tri-state (module powered off or not driving the - * signal yet). - */ - bias-pull-up; -}; - -&qup_uart4_tx { - /* We'll drive TX, so no pull */ - drive-strength = <2>; - bias-disable; -}; - -&uart4 { - status = "okay"; - /delete-property/ interrupts; - interrupts-extended = <&intc GIC_SPI 605 IRQ_TYPE_LEVEL_HIGH>, - <&tlmm 19 IRQ_TYPE_EDGE_FALLING>; - pinctrl-names = "default", "sleep"; - pinctrl-1 = <&qup_uart4_sleep_cts>, <&qup_uart4_sleep_rts>, - <&qup_uart4_sleep_tx>, <&qup_uart4_sleep_rx>; - - bluetooth: bluetooth { - compatible = "qcom,qcc2072-bt"; - max-speed = <3200000>; - }; -};