Re: [PATCH v2 2/6] Bluetooth: qca: add QCC2072 support
Rahul Samana <[email protected]> Fri, 31 Jul 2026 23:21:46 +0530
| Newsgroups | org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <[email protected]> |
On 31-07-2026 20:39, Dmitry Baryshkov wrote: > On Mon, Jul 27, 2026 at 09:15:02PM +0530, Rahul Samana wrote: >> From: Vivek Sahu <[email protected]> >> >> QCC2072 uses the ORN firmware and NVM naming scheme. The tested 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. >> >> Register the QCC2072 compatible with hci_qca and route it through the same >> UART setup, speed switching and power-control paths as recent Qualcomm >> Bluetooth controllers. >> >> This continues the QCC2072 enablement work previously posted by Vivek Sahu >> and later extended by Yepuri Siddu for the RB3 Gen 2 Industrial >> BT-over-UART use case. >> >> Signed-off-by: Vivek Sahu <[email protected]> >> Co-developed-by: Yepuri Siddu <[email protected]> >> Signed-off-by: Yepuri Siddu <[email protected]> >> Co-developed-by: Rahul Samana <[email protected]> >> Signed-off-by: Rahul Samana <[email protected]> >> --- >> drivers/bluetooth/btqca.c | 72 ++++++++++++++++++++++++++++++++++++++++++++- >> drivers/bluetooth/btqca.h | 2 ++ >> drivers/bluetooth/hci_qca.c | 24 +++++++++++++++ >> 3 files changed, 97 insertions(+), 1 deletion(-) >> >> diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c >> index 10c496eaea2c..34bc0684cf1b 100644 >> --- a/drivers/bluetooth/btqca.c >> +++ b/drivers/bluetooth/btqca.c >> @@ -569,6 +569,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, >> @@ -614,6 +662,11 @@ static int qca_download_firmware(struct hci_dev *hdev, >> memcpy(data, fw->data, size); >> release_firmware(fw); >> >> + 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) >> goto out; >> @@ -845,6 +898,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, >> snprintf(config.fwname, sizeof(config.fwname), >> "qca/hmtbtfw%02x.tlv", rom_ver); >> break; >> + case QCA_QCC2072: >> + snprintf(config.fwname, sizeof(config.fwname), >> + "qca/ornbtfw%02x.tlv", rom_ver); >> + break; > > Please try to keep these sorted. > >> default: >> snprintf(config.fwname, sizeof(config.fwname), >> "qca/rampatch_%08x.bin", soc_ver); >> @@ -878,7 +935,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 */ >> @@ -939,12 +997,23 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, >> qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname), >> "hmtnv", soc_type, ver, rom_ver, boardid); >> break; >> + case QCA_QCC2072: >> + qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname), >> + "ornnv", soc_type, ver, >> + rom_ver, boardid); >> + break; > > Sorted > >> default: >> snprintf(config.fwname, sizeof(config.fwname), >> "qca/nvm_%08x.bin", soc_ver); >> } >> } >> >> + 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), >> @@ -1001,6 +1070,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate, >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: > > Sorted > >> /* get fw build info */ >> err = qca_read_fw_build_info(hdev); >> if (err < 0) >> diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h >> index 8f3c1b1c77b3..425133096eda 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; >> @@ -158,6 +159,7 @@ enum qca_btsoc_type { >> QCA_WCN6750, >> QCA_WCN6855, >> QCA_WCN7850, >> + QCA_QCC2072, > > Sorted > >> }; >> >> #if IS_ENABLED(CONFIG_BT_QCA) >> diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c >> index 1222f97800f4..449f0896bdac 100644 >> --- a/drivers/bluetooth/hci_qca.c >> +++ b/drivers/bluetooth/hci_qca.c >> @@ -1375,6 +1375,7 @@ static int qca_set_baudrate(struct hci_dev *hdev, uint8_t baudrate) >> >> /* Give the controller time to process the request */ >> switch (qca_soc_type(hu)) { >> + case QCA_QCC2072: > > Ok, here you've discovered sorting order. > >> case QCA_WCN3950: >> case QCA_WCN3988: >> case QCA_WCN3990: >> @@ -1462,6 +1463,7 @@ static unsigned int qca_get_speed(struct hci_uart *hu, >> static int qca_check_speeds(struct hci_uart *hu) >> { >> switch (qca_soc_type(hu)) { >> + case QCA_QCC2072: >> case QCA_WCN3950: >> case QCA_WCN3988: >> case QCA_WCN3990: >> @@ -1513,6 +1515,7 @@ static int qca_set_speed(struct hci_uart *hu, enum qca_speed_type speed_type) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: > > And then lost it again. > >> hci_uart_set_flow_control(hu, true); >> break; >> >> @@ -1548,6 +1551,7 @@ static int qca_set_speed(struct hci_uart *hu, enum qca_speed_type speed_type) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> hci_uart_set_flow_control(hu, false); >> break; >> >> @@ -1864,6 +1868,7 @@ static int qca_power_on(struct hci_dev *hdev) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> ret = qca_regulator_init(hu); >> break; >> >> @@ -1963,6 +1968,10 @@ static int qca_setup(struct hci_uart *hu) >> soc_name = "wcn7850"; >> break; >> >> + case QCA_QCC2072: >> + soc_name = "qcc2072"; >> + break; >> + >> default: >> soc_name = "ROME/QCA6390"; >> } >> @@ -1986,6 +1995,7 @@ static int qca_setup(struct hci_uart *hu) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> if (qcadev && qcadev->bdaddr_property_broken) >> hci_set_quirk(hdev, HCI_QUIRK_BDADDR_PROPERTY_BROKEN); >> >> @@ -2019,6 +2029,7 @@ static int qca_setup(struct hci_uart *hu) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> break; >> >> default: >> @@ -2172,6 +2183,12 @@ static const struct qca_device_data qca_soc_data_wcn3998 __maybe_unused = { >> .num_vregs = 4, >> }; >> >> +static const struct qca_device_data qca_soc_data_qcc2072 __maybe_unused = { >> + .soc_type = QCA_QCC2072, >> + .num_vregs = 0, >> + .capabilities = QCA_CAP_WIDEBAND_SPEECH | QCA_CAP_VALID_LE_STATES, >> +}; >> + >> static const struct qca_device_data qca_soc_data_wcn6750 __maybe_unused = { >> .soc_type = QCA_WCN6750, >> .vregs = (struct qca_vreg []) { >> @@ -2274,6 +2291,7 @@ static void qca_power_off(struct hci_uart *hu) >> >> case QCA_WCN6750: >> case QCA_WCN6855: >> + case QCA_QCC2072: >> gpiod_set_value_cansleep(qcadev->bt_en, 0); >> msleep(100); >> qca_regulator_disable(qcadev); >> @@ -2429,6 +2447,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> qcadev->bt_power = devm_kzalloc(&serdev->dev, >> sizeof(struct qca_power), >> GFP_KERNEL); >> @@ -2448,6 +2467,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) >> case QCA_WCN6750: >> case QCA_WCN6855: >> case QCA_WCN7850: >> + case QCA_QCC2072: >> if (!device_property_present(&serdev->dev, "enable-gpios")) { >> /* >> * Backward compatibility with old DT sources. If the >> @@ -2490,6 +2510,7 @@ 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_QCC2072 || > > Randomly in the middle. No > >> data->soc_type == QCA_WCN7850)) >> power_ctrl_enabled = false; >> >> @@ -2498,6 +2519,7 @@ static int qca_serdev_probe(struct serdev_device *serdev) >> if (IS_ERR(qcadev->sw_ctrl) && >> (data->soc_type == QCA_WCN6750 || >> data->soc_type == QCA_WCN6855 || >> + data->soc_type == QCA_QCC2072 || >> data->soc_type == QCA_WCN7850)) { >> dev_err(&serdev->dev, "failed to acquire SW_CTRL gpio\n"); >> return PTR_ERR(qcadev->sw_ctrl); >> @@ -2576,6 +2598,7 @@ static void qca_serdev_remove(struct serdev_device *serdev) >> struct qca_power *power = qcadev->bt_power; >> >> switch (qcadev->btsoc_type) { >> + case QCA_QCC2072: >> case QCA_WCN3988: >> case QCA_WCN3990: >> case QCA_WCN3991: >> @@ -2785,6 +2808,7 @@ static const struct of_device_id qca_bluetooth_of_match[] = { >> { .compatible = "qcom,wcn6750-bt", .data = &qca_soc_data_wcn6750}, >> { .compatible = "qcom,wcn6855-bt", .data = &qca_soc_data_wcn6855}, >> { .compatible = "qcom,wcn7850-bt", .data = &qca_soc_data_wcn7850}, >> + { .compatible = "qcom,qcc2072-bt", .data = &qca_soc_data_qcc2072 }, > > And here too. Keep those sorted. > Hi Dmitry, Ack, I will keep the entries sorted consistently in v3. Thanks, Rahul >> { /* sentinel */ } >> }; >> MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match); >> >> -- >> 2.34.1 >> >