diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index bdc85147b97f..75483d8230e1 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -22,6 +22,12 @@ #define MTK_FW_ROM_PATCH_SEC_MAP_SIZE 64 #define MTK_SEC_MAP_COMMON_SIZE 12 #define MTK_SEC_MAP_NEED_SEND_SIZE 52 +#define MTK_SEC_MAP_LENGTH_SIZE 4 +#define MTK_SEC_CBMCU_DESC 0x5 + +/* CBMCU WMT command flags */ +#define BTMTK_CBMCU_FLAG_QUERY_STATUS 0xF0 +#define BTMTK_CBMCU_FLAG_ENABLE_PATCH 0xF1 /* It is for mt79xx iso data transmission setting */ #define MTK_ISO_THRESHOLD 264 @@ -182,6 +188,11 @@ void btmtk_fw_get_filename(char *buf, size_t size, u32 dev_id, u32 fw_ver, snprintf(buf, size, "mediatek/mt%04x/BT_RAM_CODE_MT%04x_1_%x_hdr.bin", dev_id & 0xffff, dev_id & 0xffff, (fw_ver & 0xff) + 1); + /* MT7928 */ + else if (dev_id == 0x7935) + snprintf(buf, size, + "mediatek/mt7928/BT_RAM_CODE_MT%04x_1_1_hdr.bin", + dev_id & 0xffff); else if (dev_id == 0x7961 && fw_flavor) snprintf(buf, size, "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", @@ -809,6 +820,7 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_ON_UNDONE; break; case BTMTK_WMT_PATCH_DWNLD: + case BTMTK_WMT_CBMCU_DWNLD: if (wmt_evt->whdr.flag == 2) status = BTMTK_WMT_PATCH_DONE; else if (wmt_evt->whdr.flag == 1) @@ -933,6 +945,373 @@ static u32 btmtk_usb_reset_done(struct hci_dev *hdev) return val & MTK_BT_RST_DONE; } +static int btmtk_cbmcu_patch_status(struct hci_dev *hdev, + wmt_cmd_sync_func_t wmt_cmd_sync, + u8 *patch_status) +{ + struct btmtk_hci_wmt_params wmt_params; + int status = BTMTK_WMT_INVALID, err, retry = 20; + + do { + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD; + wmt_params.flag = BTMTK_CBMCU_FLAG_QUERY_STATUS; + wmt_params.dlen = 0; + wmt_params.data = NULL; + wmt_params.status = &status; + + err = wmt_cmd_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to query CBMCU patch status (%d)", err); + return err; + } + + *patch_status = (u8)status; + + if (*patch_status == BTMTK_WMT_PATCH_PROGRESS) { + msleep(100); + retry--; + } else { + break; + } + } while (retry > 0); + + if (*patch_status == BTMTK_WMT_PATCH_PROGRESS) { + bt_dev_err(hdev, "CBMCU patch status query timeout"); + return -ETIMEDOUT; + } + + return 0; +} + +/* Ask the firmware for the status of the given CBMCU section. A status of + * BTMTK_WMT_PATCH_UNDONE means the section has to be downloaded by the + * Bluetooth driver, and 0 is returned so the caller downloads it. A section + * the firmware already has is not expected here, because + * btmtk_setup_cbmcu_firmware() only reaches this point when the CBMCU patch + * as a whole is not downloaded, but it is not an error either: 1 is returned + * so the caller leaves that section alone. Any other status is an error. + */ +static int btmtk_query_cbmcu_section(struct hci_dev *hdev, + wmt_cmd_sync_func_t wmt_cmd_sync, + u8 cbmcu_type, + const u8 *section_map, + u32 cert_len) +{ + struct btmtk_hci_wmt_params wmt_params; + u8 cmd[64]; + int status = BTMTK_WMT_INVALID, err, retry = 20; + + cmd[0] = 0; + cmd[1] = cbmcu_type; + + if (cbmcu_type == 0) + put_unaligned_le32(cert_len, &cmd[2]); + else + memcpy(&cmd[2], section_map, MTK_SEC_MAP_NEED_SEND_SIZE); + + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD; + wmt_params.flag = 0; + wmt_params.dlen = cbmcu_type ? + MTK_SEC_MAP_NEED_SEND_SIZE + 2 : + MTK_SEC_MAP_LENGTH_SIZE + 2; + wmt_params.data = cmd; + wmt_params.status = &status; + + /* The CBMCU is shared with the Wi-Fi driver, which may be downloading + * the same firmware. Wait while the other driver holds this section, + * the same way btmtk_setup_firmware_79xx() does for the ROM patch. + */ + while (retry--) { + err = wmt_cmd_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to query CBMCU section (%d)", err); + return err; + } + + switch (status) { + case BTMTK_WMT_PATCH_UNDONE: + /* This section has to be downloaded by the BT driver */ + return 0; + case BTMTK_WMT_PATCH_DONE: + /* Already downloaded, nothing to do for this section */ + return 1; + case BTMTK_WMT_PATCH_PROGRESS: + msleep(100); + break; + default: + bt_dev_err(hdev, "CBMCU section query status error (%d)", + status); + return -EIO; + } + } + + bt_dev_err(hdev, "CBMCU section query timeout"); + return -ETIMEDOUT; +} + +static int btmtk_download_cbmcu_section(struct hci_dev *hdev, + wmt_cmd_sync_func_t wmt_cmd_sync, + const u8 *fw_data, + u32 dl_size) +{ + struct btmtk_hci_wmt_params wmt_params; + u32 sent_len, total_size = dl_size; + int err; + + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD; + wmt_params.status = NULL; + + while (dl_size > 0) { + sent_len = min_t(u32, 250, dl_size); + + if (dl_size == total_size) + wmt_params.flag = BTMTK_WMT_PKT_START; + else if (dl_size == sent_len) + wmt_params.flag = BTMTK_WMT_PKT_END; + else + wmt_params.flag = BTMTK_WMT_PKT_CONTINUE; + + wmt_params.dlen = sent_len; + wmt_params.data = fw_data; + + err = wmt_cmd_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to send CBMCU section data (%d)", err); + return err; + } + + dl_size -= sent_len; + fw_data += sent_len; + } + + return 0; +} + +static int btmtk_enable_cbmcu_patch(struct hci_dev *hdev, + wmt_cmd_sync_func_t wmt_cmd_sync) +{ + struct btmtk_hci_wmt_params wmt_params; + int err; + + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD; + wmt_params.flag = BTMTK_CBMCU_FLAG_ENABLE_PATCH; + wmt_params.dlen = 0; + wmt_params.data = NULL; + wmt_params.status = NULL; + + err = wmt_cmd_sync(hdev, &wmt_params); + if (err < 0) { + bt_dev_err(hdev, "Failed to enable CBMCU patch (%d)", err); + return err; + } + + return 0; +} + +static int btmtk_load_cbmcu_firmware(struct hci_dev *hdev, + const char *fwname, + wmt_cmd_sync_func_t wmt_cmd_sync, + u32 dev_id) +{ + struct btmtk_patch_header *hdr; + struct btmtk_section_map *sectionmap; + const struct firmware *fw; + const u8 *fw_ptr; + u8 *cert_buf = NULL; + u32 section_num, section_offset, dl_size, i; + size_t map_len, cert_len; + bool phase1_done = false; + int err; + + err = request_firmware(&fw, fwname, &hdev->dev); + if (err < 0) { + bt_dev_err(hdev, "Failed to load CBMCU firmware file %s (%d)", + fwname, err); + return err; + } + + err = btmtk_fw_validate_layout(hdev, fw, §ion_num); + if (err < 0) + goto err_release_fw; + + fw_ptr = fw->data; + hdr = (struct btmtk_patch_header *)fw_ptr; + + bt_dev_info(hdev, "CBMCU HW ver: 0x%04x, SW ver: 0x%04x, Build Time: %.16s", + dev_id & 0xffff, le16_to_cpu(hdr->swver), hdr->datetime); + + /* Phase 1: Download section type MTK_SEC_CBMCU_DESC */ + for (i = 0; i < section_num; i++) { + sectionmap = (struct btmtk_section_map *) + (fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE + + MTK_FW_ROM_PATCH_GD_SIZE + + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i); + + /* Only process MTK_SEC_CBMCU_DESC section in Phase 1 */ + if ((le32_to_cpu(sectionmap->sectype) & 0xFFFF) != MTK_SEC_CBMCU_DESC) + continue; + + section_offset = le32_to_cpu(sectionmap->secoffset); + /* Unlike the sections downloaded in Phase 2, this one carries + * the signature blob rather than a download target, so its + * length is secsize and not bin_info_spec.dlsize. + */ + dl_size = le32_to_cpu(sectionmap->secsize); + + if (dl_size == 0) + continue; + + err = btmtk_fw_validate_section(hdev, fw, i, + section_offset, dl_size); + if (err < 0) + goto err_release_fw; + + map_len = MTK_FW_ROM_PATCH_GD_SIZE + + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num; + if (check_add_overflow(map_len, (size_t)dl_size, &cert_len) || + cert_len > U32_MAX) { + err = -EINVAL; + goto err_release_fw; + } + + /* Build the certificate before the query, because the firmware + * hands this section over to whoever is told to download it. + */ + cert_buf = kmalloc(cert_len, GFP_KERNEL | __GFP_NOWARN); + if (!cert_buf) { + err = -ENOMEM; + goto err_release_fw; + } + + /* Global Descriptor + All Section Maps, then the section data */ + memcpy(cert_buf, fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE, map_len); + memcpy(cert_buf + map_len, fw_ptr + section_offset, dl_size); + + /* Query cbmcu section */ + err = btmtk_query_cbmcu_section(hdev, wmt_cmd_sync, 0, NULL, + cert_len); + if (err < 0) + goto err_release_fw; + + /* Download the section unless the firmware already has it */ + if (!err) { + err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync, + cert_buf, cert_len); + if (err < 0) { + bt_dev_err(hdev, "Failed to download CBMCU Phase 1 section (%d)", + err); + goto err_release_fw; + } + } + + kfree(cert_buf); + cert_buf = NULL; + phase1_done = true; + break; + } + + if (!phase1_done) { + bt_dev_err(hdev, "CBMCU firmware has no Phase 1 section"); + err = -EINVAL; + goto err_release_fw; + } + + /* Phase 2: Download other sections (type != MTK_SEC_CBMCU_DESC) */ + for (i = 0; i < section_num; i++) { + sectionmap = (struct btmtk_section_map *) + (fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE + + MTK_FW_ROM_PATCH_GD_SIZE + + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i); + + /* Skip MTK_SEC_CBMCU_DESC section in Phase 2 */ + if ((le32_to_cpu(sectionmap->sectype) & 0xFFFF) == MTK_SEC_CBMCU_DESC) + continue; + + section_offset = le32_to_cpu(sectionmap->secoffset); + dl_size = le32_to_cpu(sectionmap->bin_info_spec.dlsize); + + if (dl_size == 0) + continue; + + err = btmtk_fw_validate_section(hdev, fw, i, + section_offset, dl_size); + if (err < 0) + goto err_release_fw; + + /* Query cbmcu section */ + err = btmtk_query_cbmcu_section(hdev, wmt_cmd_sync, 1, + (u8 *)§ionmap->bin_info_spec, + 0); + if (err < 0) + goto err_release_fw; + + /* Nothing to do if the firmware already has this section */ + if (err) + continue; + + /* Download section data */ + err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync, + fw_ptr + section_offset, + dl_size); + if (err < 0) { + bt_dev_err(hdev, "Failed to download CBMCU section %u (%d)", i, err); + goto err_release_fw; + } + } + + /* A section the firmware already had leaves err > 0 */ + err = 0; + bt_dev_info(hdev, "CBMCU firmware download completed"); + +err_release_fw: + kfree(cert_buf); + release_firmware(fw); + return err; +} + +static int btmtk_setup_cbmcu_firmware(struct hci_dev *hdev, + wmt_cmd_sync_func_t wmt_cmd_sync, + u32 dev_id) +{ + char cbmcu_fwname[64]; + u8 patch_status; + int err; + + err = btmtk_cbmcu_patch_status(hdev, wmt_cmd_sync, &patch_status); + if (err < 0) + return err; + + bt_dev_dbg(hdev, "CBMCU patch status: 0x%02x", patch_status); + + /* Nothing to do if the firmware is already running */ + if (patch_status == BTMTK_WMT_PATCH_DONE) + return 0; + + if (patch_status != BTMTK_WMT_PATCH_UNDONE) { + bt_dev_err(hdev, "Unexpected CBMCU patch status (0x%02x)", + patch_status); + return -EIO; + } + + snprintf(cbmcu_fwname, sizeof(cbmcu_fwname), + "mediatek/mt7928/CBMCU_CODE_MT%04x_1_1.bin", + dev_id & 0xffff); + + bt_dev_info(hdev, "Loading CBMCU firmware: %s", cbmcu_fwname); + + err = btmtk_load_cbmcu_firmware(hdev, cbmcu_fwname, wmt_cmd_sync, dev_id); + if (err < 0) { + bt_dev_err(hdev, "Failed to download CBMCU firmware (%d)", err); + return err; + } + + err = btmtk_enable_cbmcu_patch(hdev, wmt_cmd_sync); + if (err < 0) + return err; + + return 0; +} + int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) { int reset_err = 0; @@ -997,6 +1376,14 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err) return err; msleep(100); + } else if (dev_id == 0x7935) { + err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); + if (err) + return err; + val |= BIT(31); + err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); + if (err < 0) + return err; } else { /* It's Device EndPoint Reset Option Register */ bt_dev_dbg(hdev, "Initiating reset mechanism via uhw"); @@ -1451,6 +1838,20 @@ int btmtk_usb_setup(struct hci_dev *hdev) case 0x7668: fwname = FIRMWARE_MT7668; break; + case 0x7935: + /* Requires CBMCU firmware before BT firmware */ + err = btmtk_setup_cbmcu_firmware(hdev, btmtk_usb_hci_wmt_sync, + dev_id); + /* Carry on with the BT firmware even if this failed. The Wi-Fi + * driver may be downloading the same CBMCU firmware and finish + * it on its own, and if the CBMCU is left unusable the BT + * firmware download below fails and triggers the one-shot + * btmtk_reset_sync() recovery. + */ + if (err < 0) + bt_dev_warn(hdev, "Failed to set up CBMCU firmware (%d)", + err); + fallthrough; case 0x7922: case 0x7925: case 0x7961: @@ -1663,3 +2064,5 @@ MODULE_FIRMWARE(FIRMWARE_MT7961); MODULE_FIRMWARE(FIRMWARE_MT7920); MODULE_FIRMWARE(FIRMWARE_MT7925); MODULE_FIRMWARE(FIRMWARE_MT7927); +MODULE_FIRMWARE(FIRMWARE_MT7928); +MODULE_FIRMWARE(FIRMWARE_MT7928_CBMCU); diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index e8ad281a93a0..58643ec39667 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -10,6 +10,8 @@ #define FIRMWARE_MT7920 "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" #define FIRMWARE_MT7925 "mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin" #define FIRMWARE_MT7927 "mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin" +#define FIRMWARE_MT7928 "mediatek/mt7928/BT_RAM_CODE_MT7935_1_1_hdr.bin" +#define FIRMWARE_MT7928_CBMCU "mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin" #define HCI_EV_WMT 0xe4 #define HCI_WMT_MAX_EVENT_SIZE 64 @@ -55,6 +57,7 @@ enum { BTMTK_WMT_RST = 0x7, BTMTK_WMT_REGISTER = 0x8, BTMTK_WMT_SEMAPHORE = 0x17, + BTMTK_WMT_CBMCU_DWNLD = 0x58, }; enum {