[PATCH 4/4] Bluetooth: qca: combine NVM and calibration data for QCC2072
From: Yepuri Siddu
Date: Fri May 29 2026 - 14:06:29 EST
QCC2072 requires the NVM and calibration data to be delivered to the
controller bundled together in an outer TLV of type 4. After loading
the NVM file, load the calibration file (qca/ornbcscal<ver>.bin) and
combine both into a single buffer with the outer TLV header before
passing it to qca_tlv_check_data().
The outer TLV header encodes the combined payload length in the high
24 bits and type 4 in the low 8 bits of the type_len field.
If the calibration file is unavailable, fall back to downloading the
NVM alone.
Signed-off-by: Yepuri Siddu <yepuri.siddu@xxxxxxxxxxxxxxxx>
---
drivers/bluetooth/btqca.c | 47 +++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
index 0ef7546e7c7a..37db1cd9e8cf 100644
--- a/drivers/bluetooth/btqca.c
+++ b/drivers/bluetooth/btqca.c
@@ -612,6 +612,53 @@ 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:
+ ;
+ }
+
ret = qca_tlv_check_data(hdev, config, data, size, soc_type);
if (ret)
goto out;
--
2.34.1