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