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
>>
>