Re: [PATCH v2 2/6] Bluetooth: qca: add QCC2072 support

From: Rahul Samana

Date: Fri Jul 31 2026 - 13:56:38 EST




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 <vivek.sahu@xxxxxxxxxxxxxxxx>
>>
>> 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 <vivek.sahu@xxxxxxxxxxxxxxxx>
>> Co-developed-by: Yepuri Siddu <yepuri.siddu@xxxxxxxxxxxxxxxx>
>> Signed-off-by: Yepuri Siddu <yepuri.siddu@xxxxxxxxxxxxxxxx>
>> Co-developed-by: Rahul Samana <rahul.samana@xxxxxxxxxxxxxxxx>
>> Signed-off-by: Rahul Samana <rahul.samana@xxxxxxxxxxxxxxxx>
>> ---
>> 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
>>
>