Re: [PATCH v2 2/6] Bluetooth: qca: add QCC2072 support
Dmitry Baryshkov <[email protected]> Fri, 31 Jul 2026 18:09:47 +0300
| 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 | <53mokww7o5hky34fhwyje7utpqu42md5uknqn2b5km22ccsfzi@fsbf2pth6ztj> |
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. > { /* sentinel */ } > }; > MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match); > > -- > 2.34.1 > -- With best wishes Dmitry