* [PATCH v3] Bluetooth: btmtk: Add MT7928 support
@ 2026-10-06 3:23 Chris Lu
0 siblings, 0 replies; 4+ messages in thread
From: Chris Lu @ 2026-10-06 3:23 UTC (permalink / raw)
To: Marcel Holtmann, Johan Hedberg, Luiz Von Dentz
Cc: Sean Wang, Will Lee, SS Wu, linux-bluetooth, linux-kernel,
linux-mediatek, Chris Lu
Add support for MT7928 (internal device ID is MT7935) which requires
additional firmware (CBMCU firmware) loading before Bluetooth firmware.
CBMCU is a new component on MT7928 to handle common part shared across
the combo chip, providing a better user experience through improved
coordination between subsystems.
Implement two-phase CBMCU firmware download: Phase 1 loads section with
type 0x5 containing global descriptor, section maps and signature data;
Phase 2 loads remaining firmware sections. The firmware is rejected if it
has no Phase 1 section.
The CBMCU is shared with the Wi-Fi driver, which may download the same
firmware concurrently. The firmware reports a status for each section and
marks it as in progress as soon as it answers that it needs downloading,
so only one of the two drivers is ever told to download a given section
and no host side lock is needed. This is the same scheme that
btmtk_setup_firmware_79xx() uses for the ROM patch shared with Wi-Fi.
A driver that is told the section needs downloading downloads it, one
that sees it being downloaded by the other driver waits for it, and one
that sees it already done skips it. If the other driver downloaded every
section, wait for it to finish before loading the BT firmware, and leave
enabling the patch to it.
After CBMCU firmware loads successfully, the driver continues to load
corresponding BT firmware based on device ID through fallthrough to
case 0x7922/0x7925.
The CBMCU firmware layout and section-map bounds are validated by
reusing btmtk_fw_validate_layout() and btmtk_fw_validate_section(),
the same helpers btmtk_setup_firmware_79xx() uses, instead of
duplicating the overflow checks.
Add the MT7928 (0x7935) case to btmtk_usb_subsys_reset(), which triggers
the subsystem reset by setting BIT(31) of MTK_BT_RESET_REG_CONNV3.
MT7928 bring-up kernel log:
[ 6931.197167] usb 1-3: New USB device found, idVendor=0e8d, idProduct=7935, bcdDevice= 1.00
[ 6931.197212] usb 1-3: New USB device strings: Mfr=5, Product=6, SerialNumber=7
[ 6931.197237] usb 1-3: Product: Wireless_Device
[ 6931.197258] usb 1-3: Manufacturer: MediaTek Inc.
[ 6931.197279] usb 1-3: SerialNumber: 000000000
[ 6931.213478] Bluetooth: hci1: Loading CBMCU firmware: mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin
[ 6931.215214] Bluetooth: hci1: CBMCU HW ver: 0x7935, SW ver: 0x0000, Build Time: 20260601T161751+
[ 6931.623962] Bluetooth: hci1: CBMCU firmware download completed
[ 6931.643916] Bluetooth: hci1: Loading BT firmware: mediatek/mt7928/BT_RAM_CODE_MT7935_1_1_hdr.bin
[ 6931.650467] Bluetooth: hci1: BT HW ver: 0x7935, SW ver: 0x0000, Build Time: 20260527000816
[ 6935.039790] Bluetooth: hci1: Device setup in 3369644 usecs
[ 6935.039833] Bluetooth: hci1: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
[ 6935.160654] Bluetooth: hci1: AOSP extensions version v2.00
[ 6935.160710] Bluetooth: hci1: AOSP quality report is supported
[ 6935.162954] Bluetooth: MGMT ver 1.23
Signed-off-by: Chris Lu <chris.lu@mediatek.com>
Assisted-by: Claude:Sonnet-5
---
Changes in v3:
- btmtk_query_cbmcu_section() callers handle -EALREADY per section instead of
treating it as the whole CBMCU download being done: skip only the
section that is already downloaded and keep going
- If another driver downloaded every section, wait for it to finish before
loading the BT firmware, and leave enabling the patch to it
- Use check_add_overflow() for the Phase 1 certificate length
- Document the per-section status reported by the firmware, which also
keeps both drivers from downloading the same section
Changes in v2:
- btmtk_query_cbmcu_section(): wait while another agent (e.g. the Wi-Fi driver)
is downloading the CBMCU firmware, and skip the download if it completes
instead of failing the probe with -EIO
drivers/bluetooth/btmtk.c | 407 ++++++++++++++++++++++++++++++++++++++
drivers/bluetooth/btmtk.h | 3 +
2 files changed, 410 insertions(+)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index 115a11fcb254..cc2861cd801f 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,382 @@ 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;
+}
+
+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 firmware at the same time. The firmware reports a status for
+ * each section and marks the section as PROGRESS as soon as it
+ * answers UNDONE to a query, so only one of the two drivers is ever
+ * told to download a given section. UNDONE means this driver should
+ * download it, PROGRESS means the other driver is downloading it and
+ * we wait, and DONE means it is already downloaded and we skip it
+ * (-EALREADY). This is the same scheme btmtk_setup_firmware_79xx()
+ * uses for the ROM patch shared with Wi-Fi, so no host side lock is
+ * needed.
+ */
+ 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:
+ return 0;
+ case BTMTK_WMT_PATCH_PROGRESS:
+ msleep(100);
+ break;
+ case BTMTK_WMT_PATCH_DONE:
+ return -EALREADY;
+ 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, bool *downloaded)
+{
+ 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, cert_len;
+ bool phase1_done = false;
+ int i, err;
+
+ *downloaded = false;
+
+ 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 +
+ 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);
+ 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;
+
+ if (check_add_overflow((u32)(MTK_FW_ROM_PATCH_GD_SIZE +
+ MTK_FW_ROM_PATCH_SEC_MAP_SIZE *
+ section_num),
+ dl_size, &cert_len)) {
+ err = -EINVAL;
+ goto err_release_fw;
+ }
+
+ /* Query cbmcu section */
+ err = btmtk_query_cbmcu_section(hdev, wmt_cmd_sync, 0, NULL,
+ cert_len);
+ if (err == -EALREADY) {
+ /* Already downloaded by the other driver */
+ phase1_done = true;
+ break;
+ }
+ if (err < 0)
+ goto err_release_fw;
+
+ cert_buf = kmalloc(cert_len, GFP_KERNEL);
+ if (!cert_buf) {
+ err = -ENOMEM;
+ goto err_release_fw;
+ }
+
+ /* Copy Global Descriptor + All Section Maps */
+ memcpy(cert_buf,
+ fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE,
+ MTK_FW_ROM_PATCH_GD_SIZE + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num);
+
+ /* Copy Phase 1 section data */
+ memcpy(cert_buf + MTK_FW_ROM_PATCH_GD_SIZE +
+ MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
+ fw_ptr + section_offset,
+ dl_size);
+
+ /* Download Phase 1 section */
+ err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync,
+ cert_buf, cert_len);
+ kfree(cert_buf);
+ cert_buf = NULL;
+
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to download CBMCU Phase 1 section (%d)", err);
+ goto err_release_fw;
+ }
+
+ *downloaded = true;
+ 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 +
+ 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 == -EALREADY)
+ continue;
+ if (err < 0)
+ goto err_release_fw;
+
+ /* 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 %d (%d)", i, err);
+ goto err_release_fw;
+ }
+
+ *downloaded = true;
+ }
+
+ err = 0;
+
+err_release_fw:
+ 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];
+ bool downloaded;
+ 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);
+
+ if (patch_status != BTMTK_WMT_PATCH_UNDONE)
+ return 0;
+
+ 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,
+ &downloaded);
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to download CBMCU firmware (%d)", err);
+ return err;
+ }
+
+ if (!downloaded) {
+ /* The other driver is downloading or has downloaded every
+ * section; wait until it is done before loading BT firmware.
+ */
+ bt_dev_info(hdev, "CBMCU firmware downloaded by another driver");
+ err = btmtk_cbmcu_patch_status(hdev, wmt_cmd_sync,
+ &patch_status);
+ if (err < 0)
+ return err;
+
+ if (patch_status != BTMTK_WMT_PATCH_DONE)
+ bt_dev_warn(hdev, "CBMCU patch status is 0x%02x, not done",
+ patch_status);
+
+ return 0;
+ }
+
+ bt_dev_info(hdev, "CBMCU firmware download completed");
+
+ return btmtk_enable_cbmcu_patch(hdev, wmt_cmd_sync);
+}
+
int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
{
int reset_err = 0;
@@ -997,6 +1385,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");
@@ -1455,6 +1851,15 @@ 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);
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to set up CBMCU firmware (%d)", err);
+ return err;
+ }
+ fallthrough;
case 0x7922:
case 0x7925:
case 0x7961:
@@ -1667,3 +2072,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 {
base-commit: 036d4119079a757c9d1b4d35205c7c467f0018fb
--
2.45.2
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] Bluetooth: btmtk: Add MT7928 support
2026-06-18 5:30 ` Paul Menzel
@ 2026-06-22 5:32 ` Chris Lu (陸稚泓)
0 siblings, 0 replies; 4+ messages in thread
From: Chris Lu (陸稚泓) @ 2026-06-22 5:32 UTC (permalink / raw)
To: pmenzel
Cc: Will-CY Lee (李政穎),
Steve Lee (李視誠),
luiz.dentz, marcel, SS Wu (巫憲欣),
linux-kernel, johan.hedberg, Sean Wang, linux-bluetooth,
linux-mediatek
Hi Paul,
On Thu, 2026-06-18 at 07:30 +0200, Paul Menzel wrote:
>
> External email : Please do not click links or open attachments until
> you have verified the sender or the content.
>
>
> Am 18.06.26 um 01:51 schrieb Chris Lu:
> > Add support for MT7928 (internal device ID is MT7935) which
> > requires additional firmware (CBMCU firmware) loading before
> > Bluetooth firmware.
> >
> > CBMCU is a new component on MT7928 to handle common part shared
> > across the combo chip (Wi-Fi/Bluetooth's subsystem), providing
> > a better user experience through improved coordination between
> > subsystems.
> >
> > Implement two-phase CBMCU firmware download: Phase 1 loads
> > section with type 0x5 containing global descriptor,
> > section maps and signature data; Phase 2 loads remaining
> > firmware sections. Add retry mechanism for concurrent download
> > protection.
> >
> > After CBMCU firmware loads successfully, the driver continues
> > to load corresponding BT firmware based on device ID through
> > fallthrough to case 0x7922/0x7925.
> >
> > The firmware required for MT7928 will be scheduled for upload
> > to linux-firmware at a later stage.
>
> Should you resend please re-flow for 72 characters per line to use
> less
> lines.
>
> Also, please mention the document name, revision and file name, where
> the CBMCU format is described.
I'll reformat the line breaks and add firmware name information for
MT7928 in commit message.
>
> > MT7928 bringup kernel log:
> > [90.209995] usb 1-3: New USB device found, idVendor=0e8d,
> > idProduct=7935, bcdDevice= 1.00
> > [90.210027] usb 1-3: New USB device strings: Mfr=5,
> > Product=6, SerialNumber=7
> > [90.210046] usb 1-3: Product: Wireless_Device
> > [90.210060] usb 1-3: Manufacturer: MediaTek Inc.
> > [90.210075] usb 1-3: SerialNumber: 000000000
> > [90.223089] Bluetooth: hci1: CBMCU Version: 0x00000000,
> > Build Time: 20260601T161751+0800
> > [90.664706] Bluetooth: hci1: CBMCU firmware download completed
> > [90.685424] Bluetooth: hci1: HW/SW Version: 0x00000000,
> > Build Time: 20260527000816
> > [93.771612] Bluetooth: hci1: Device setup in 3467323 usecs
>
> Wow, over three seconds is too long. Can you please check how to
> bring
> this below one second.
Due to current driver design, we can only use control URBs with shorter
data lengths for setup. Our previous IC also took nearly three seconds
to download firmware. MediaTek Bluetooth team's next improvement goal
will focus on reducing setup time.
>
> > [93.771657] Bluetooth: hci1: HCI Enhanced Setup Synchronous
> > Connection command is advertised, but not supported.
> > [93.890840] Bluetooth: hci1: AOSP extensions version v2.00
> > [93.890887] Bluetooth: hci1: AOSP quality report is supported
> > [93.893444] Bluetooth: MGMT ver 1.23
>
> Please do not wrap pasted logs. `scripts/checkpatch.pl` should not
> complain:
Because the previous submission, this section was detected by bluetooth
check robot for exceeding character limit. I will restore its original
form in next submission if wrapping is not required.
>
> ```
> # Check if the commit log is in a possible stack dump
> if ($in_commit_log &&
> !$commit_log_possible_stack_dump &&
> ($line =~ /^\s*(?:WARNING:|BUG:)/ ||
> $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ ||
> # timestamp
> $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) ||
> $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ ||
> $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at
> [0-9a-fA-F]+/) {
> # stack dump address styles
> $commit_log_possible_stack_dump = 1;
> }
> ```
>
> > Signed-off-by: Chris Lu <chris.lu@mediatek.com>
> > ---
> > v1->v2: Update error message; Use macro instead of magic number.
> > v2->v3: Update commit message.
> > ---
> > drivers/bluetooth/btmtk.c | 348
> > +++++++++++++++++++++++++++++++++++++-
> > drivers/bluetooth/btmtk.h | 3 +
> > 2 files changed, 350 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
> > index 02a96342e964..6bae0b0794dd 100644
> > --- a/drivers/bluetooth/btmtk.c
> > +++ b/drivers/bluetooth/btmtk.c
> > @@ -21,6 +21,8 @@
> > #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
> >
> > /* It is for mt79xx iso data transmission setting */
> > #define MTK_ISO_THRESHOLD 264
> > @@ -120,6 +122,10 @@ 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);
> > + else if (dev_id == 0x7935)
>
> I wonder if the marketing name should be added as a comment.
>
> > + snprintf(buf, size,
> > +
> > "mediatek/mt7928/BT_RAM_CODE_MT%04x_1_1_hdr.bin",
> > + dev_id & 0xffff);
>
> `dev_id` is u32, so the truncatation is unnecessary?
While technically %04x only prints the lower 16 bits, I prefer to keep
"& 0xffff" for consistency with all other cases in
btmtk_fw_get_filename function. The explicit masking makes the intent
clear and maintains the existing code style.
>
> > else if (dev_id == 0x7961 && fw_flavor)
> > snprintf(buf, size,
> > "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin",
> > @@ -734,6 +740,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)
> > @@ -870,6 +877,334 @@ 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, err, retry = 20;
> > +
> > + do {
> > + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> > + wmt_params.flag = 0xF0;
> > + 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);
> > +
> > + return 0;
> > +}
> > +
> > +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, err;
> > +
> > + 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;
> > +
> > + err = wmt_cmd_sync(hdev, &wmt_params);
> > + if (err < 0) {
> > + bt_dev_err(hdev, "Failed to query CBMCU section
> > (%d)", err);
> > + return err;
> > + }
> > +
> > + /* Query should return UNDONE status for successful section
> > query */
> > + if (status != BTMTK_WMT_PATCH_UNDONE) {
> > + bt_dev_err(hdev, "CBMCU section query status error
> > (%d)", status);
> > + return -EIO;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +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 = 1;
> > + else if (dl_size == sent_len)
> > + wmt_params.flag = 3;
> > + else
> > + wmt_params.flag = 2;
>
> This is not easy to read, as it’s not clear right away, what 1, 2 and
> 3
> mean. Maybe use enums?
>
> > +
> > + 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);
>
> Print the sent parameters? Or will `wmt_cmd_sync()` log something?
>
> > + 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 = 0xF1;
>
> Ditto.
>
> > + 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)
> > +{
> > + struct btmtk_patch_header *hdr;
> > + struct btmtk_global_desc *globaldesc;
> > + struct btmtk_section_map *sectionmap;
> > + const struct firmware *fw;
> > + const u8 *fw_ptr;
> > + u8 *cert_buf = NULL;
> > + u32 section_num, section_offset, dl_size, cert_len;
> > + int i, 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;
> > + }
> > +
> > + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE +
> > MTK_FW_ROM_PATCH_GD_SIZE) {
> > + bt_dev_err(hdev, "CBMCU firmware too small (%zu
> > bytes)", fw->size);
>
> Please log `MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE`.
>
> > + err = -EINVAL;
> > + goto err_release_fw;
> > + }
> > +
> > + fw_ptr = fw->data;
> > + hdr = (struct btmtk_patch_header *)fw_ptr;
> > + globaldesc = (struct btmtk_global_desc *)(fw_ptr +
> > MTK_FW_ROM_PATCH_HEADER_SIZE);
> > + section_num = le32_to_cpu(globaldesc->section_num);
> > +
> > + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE +
> > MTK_FW_ROM_PATCH_GD_SIZE +
> > + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE *
> > section_num) {
> > + bt_dev_err(hdev, "CBMCU firmware truncated: size=%zu,
> > expected=%zu (section_num=%u)",
> > + fw->size,
> > + MTK_FW_ROM_PATCH_HEADER_SIZE +
> > MTK_FW_ROM_PATCH_GD_SIZE +
> > + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE *
> > section_num,
> > + section_num);
> > + err = -EINVAL;
> > + goto err_release_fw;
> > + }
> > +
> > + bt_dev_info(hdev, "CBMCU Version: 0x%04x%04x, Build Time:
> > %s",
> > + le16_to_cpu(hdr->hwver), 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 +
> > + 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);
> > + dl_size = le32_to_cpu(sectionmap->secsize);
> > +
> > + if (dl_size == 0)
> > + continue;
> > +
> > + if (section_offset > fw->size ||
> > + dl_size > fw->size - section_offset) {
> > + bt_dev_err(hdev, "CBMCU Phase 1 section out
> > of bounds");
> > + err = -EINVAL;
> > + goto err_release_fw;
> > + }
> > +
> > + cert_len = MTK_FW_ROM_PATCH_GD_SIZE +
> > + MTK_FW_ROM_PATCH_SEC_MAP_SIZE *
> > section_num +
> > + 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;
> > +
> > + cert_buf = kmalloc(cert_len, GFP_KERNEL);
> > + if (!cert_buf) {
> > + err = -ENOMEM;
> > + goto err_release_fw;
> > + }
> > +
> > + /* Copy Global Descriptor + All Section Maps */
> > + memcpy(cert_buf,
> > + fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE,
> > + MTK_FW_ROM_PATCH_GD_SIZE +
> > MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num);
> > +
> > + /* Copy Phase 1 section data */
> > + memcpy(cert_buf + MTK_FW_ROM_PATCH_GD_SIZE +
> > + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
> > + fw_ptr + section_offset,
> > + dl_size);
> > +
> > + /* Download Phase 1 section */
> > + err = btmtk_download_cbmcu_section(hdev,
> > wmt_cmd_sync,
> > + cert_buf,
> > cert_len);
> > + kfree(cert_buf);
> > + cert_buf = NULL;
> > +
> > + if (err < 0) {
> > + bt_dev_err(hdev, "Failed to download CBMCU
> > Phase 1 section (%d)", err);
> > + goto err_release_fw;
> > + }
> > +
> > + break;
> > + }
> > +
> > + /* 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 +
> > + 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;
> > +
> > + if (section_offset > fw->size ||
> > + dl_size > fw->size - section_offset) {
> > + bt_dev_err(hdev, "CBMCU Phase 2 section %d
> > out of bounds", i);
> > + err = -EINVAL;
> > + 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;
> > +
> > + /* 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 %d (%d)", i, err);
> > + goto err_release_fw;
> > + }
> > + }
> > +
> > + /* Wait for firmware activation */
> > + usleep_range(100000, 120000);
>
> Does the firmware really take this long? (Please report to the
> firmware
> engineers to optimize this. Optimized Linux takes less time. ;-))
> Isn’t
> there a way to poll the readiness?
>
> > +
> > + bt_dev_info(hdev, "CBMCU firmware download completed");
>
> For me, Linux uploads firmware to the device (and the BT device would
> download it). But this is not a BT device message but the OS message?
> Feel free to ignore.
>
MT7928 requires two-phase firmware loading, This message distinguishes
phase boundaries phase boundaries, helpful for debugging initialization
issues.
> > +
> > +err_release_fw:
> > + 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);
> > +
> > + if (patch_status != BTMTK_WMT_PATCH_UNDONE)
> > + return 0;
> > +
> > + snprintf(cbmcu_fwname, sizeof(cbmcu_fwname),
> > + "mediatek/mt7928/CBMCU_CODE_MT%04x_1_1.bin",
> > + dev_id & 0xffff);
> > +
> > + err = btmtk_load_cbmcu_firmware(hdev, cbmcu_fwname,
> > wmt_cmd_sync);
> > + 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)
> > {
> > u32 val;
> > @@ -894,7 +1229,7 @@ int btmtk_usb_subsys_reset(struct hci_dev
> > *hdev, u32 dev_id)
> > if (err < 0)
> > return err;
> > msleep(100);
> > - } else if (dev_id == 0x7925 || dev_id == 0x6639) {
> > + } else if (dev_id == 0x7925 || dev_id == 0x6639 || dev_id ==
> > 0x7935) {
>
> This should be sorted in my opinion. Maybe add a commit before, and
> put
> the new id at the beginning.
>
> > err = btmtk_usb_uhw_reg_read(hdev,
> > MTK_BT_RESET_REG_CONNV3, &val);
> > if (err < 0)
> > return err;
> > @@ -1379,6 +1714,15 @@ 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);
> > + if (err < 0) {
> > + bt_dev_err(hdev, "Failed to set up CBMCU
> > firmware (%d)", err);
> > + return err;
> > + }
> > + fallthrough;
> > case 0x7922:
> > case 0x7925:
> > /*
> > @@ -1596,3 +1940,5 @@ MODULE_FIRMWARE(FIRMWARE_MT7922);
> > MODULE_FIRMWARE(FIRMWARE_MT7961);
> > 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 c83c24897c95..6d3bf6b74a1d 100644
> > --- a/drivers/bluetooth/btmtk.h
> > +++ b/drivers/bluetooth/btmtk.h
> > @@ -9,6 +9,8 @@
> > #define FIRMWARE_MT7961
> > "mediatek/BT_RAM_CODE_MT7961_1_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
> > @@ -54,6 +56,7 @@ enum {
> > BTMTK_WMT_RST = 0x7,
> > BTMTK_WMT_REGISTER = 0x8,
> > BTMTK_WMT_SEMAPHORE = 0x17,
> > + BTMTK_WMT_CBMCU_DWNLD = 0x58,
> > };
> >
> > enum {
>
For the other pars that haven't been addressed in this mail, I will
make evaluation and improvement in v4, Thanks for your suggestions.
Chris Lu
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] Bluetooth: btmtk: Add MT7928 support
2026-06-17 23:51 Chris Lu
@ 2026-06-18 5:30 ` Paul Menzel
2026-06-22 5:32 ` Chris Lu (陸稚泓)
0 siblings, 1 reply; 4+ messages in thread
From: Paul Menzel @ 2026-06-18 5:30 UTC (permalink / raw)
To: Chris Lu
Cc: Marcel Holtmann, Johan Hedberg, Luiz Von Dentz, Sean Wang,
Will Lee, SS Wu, Steve Lee, linux-bluetooth, linux-kernel,
linux-mediatek
Am 18.06.26 um 01:51 schrieb Chris Lu:
> Add support for MT7928 (internal device ID is MT7935) which
> requires additional firmware (CBMCU firmware) loading before
> Bluetooth firmware.
>
> CBMCU is a new component on MT7928 to handle common part shared
> across the combo chip (Wi-Fi/Bluetooth's subsystem), providing
> a better user experience through improved coordination between
> subsystems.
>
> Implement two-phase CBMCU firmware download: Phase 1 loads
> section with type 0x5 containing global descriptor,
> section maps and signature data; Phase 2 loads remaining
> firmware sections. Add retry mechanism for concurrent download
> protection.
>
> After CBMCU firmware loads successfully, the driver continues
> to load corresponding BT firmware based on device ID through
> fallthrough to case 0x7922/0x7925.
>
> The firmware required for MT7928 will be scheduled for upload
> to linux-firmware at a later stage.
Should you resend please re-flow for 72 characters per line to use less
lines.
Also, please mention the document name, revision and file name, where
the CBMCU format is described.
> MT7928 bringup kernel log:
> [90.209995] usb 1-3: New USB device found, idVendor=0e8d,
> idProduct=7935, bcdDevice= 1.00
> [90.210027] usb 1-3: New USB device strings: Mfr=5,
> Product=6, SerialNumber=7
> [90.210046] usb 1-3: Product: Wireless_Device
> [90.210060] usb 1-3: Manufacturer: MediaTek Inc.
> [90.210075] usb 1-3: SerialNumber: 000000000
> [90.223089] Bluetooth: hci1: CBMCU Version: 0x00000000,
> Build Time: 20260601T161751+0800
> [90.664706] Bluetooth: hci1: CBMCU firmware download completed
> [90.685424] Bluetooth: hci1: HW/SW Version: 0x00000000,
> Build Time: 20260527000816
> [93.771612] Bluetooth: hci1: Device setup in 3467323 usecs
Wow, over three seconds is too long. Can you please check how to bring
this below one second.
> [93.771657] Bluetooth: hci1: HCI Enhanced Setup Synchronous
> Connection command is advertised, but not supported.
> [93.890840] Bluetooth: hci1: AOSP extensions version v2.00
> [93.890887] Bluetooth: hci1: AOSP quality report is supported
> [93.893444] Bluetooth: MGMT ver 1.23
Please do not wrap pasted logs. `scripts/checkpatch.pl` should not complain:
```
# Check if the commit log is in a possible stack dump
if ($in_commit_log && !$commit_log_possible_stack_dump &&
($line =~ /^\s*(?:WARNING:|BUG:)/ ||
$line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ ||
# timestamp
$line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) ||
$line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ ||
$line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at
[0-9a-fA-F]+/) {
# stack dump address styles
$commit_log_possible_stack_dump = 1;
}
```
> Signed-off-by: Chris Lu <chris.lu@mediatek.com>
> ---
> v1->v2: Update error message; Use macro instead of magic number.
> v2->v3: Update commit message.
> ---
> drivers/bluetooth/btmtk.c | 348 +++++++++++++++++++++++++++++++++++++-
> drivers/bluetooth/btmtk.h | 3 +
> 2 files changed, 350 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
> index 02a96342e964..6bae0b0794dd 100644
> --- a/drivers/bluetooth/btmtk.c
> +++ b/drivers/bluetooth/btmtk.c
> @@ -21,6 +21,8 @@
> #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
>
> /* It is for mt79xx iso data transmission setting */
> #define MTK_ISO_THRESHOLD 264
> @@ -120,6 +122,10 @@ 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);
> + else if (dev_id == 0x7935)
I wonder if the marketing name should be added as a comment.
> + snprintf(buf, size,
> + "mediatek/mt7928/BT_RAM_CODE_MT%04x_1_1_hdr.bin",
> + dev_id & 0xffff);
`dev_id` is u32, so the truncatation is unnecessary?
> else if (dev_id == 0x7961 && fw_flavor)
> snprintf(buf, size,
> "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin",
> @@ -734,6 +740,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)
> @@ -870,6 +877,334 @@ 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, err, retry = 20;
> +
> + do {
> + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> + wmt_params.flag = 0xF0;
> + 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);
> +
> + return 0;
> +}
> +
> +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, err;
> +
> + 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;
> +
> + err = wmt_cmd_sync(hdev, &wmt_params);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to query CBMCU section (%d)", err);
> + return err;
> + }
> +
> + /* Query should return UNDONE status for successful section query */
> + if (status != BTMTK_WMT_PATCH_UNDONE) {
> + bt_dev_err(hdev, "CBMCU section query status error (%d)", status);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +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 = 1;
> + else if (dl_size == sent_len)
> + wmt_params.flag = 3;
> + else
> + wmt_params.flag = 2;
This is not easy to read, as it’s not clear right away, what 1, 2 and 3
mean. Maybe use enums?
> +
> + 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);
Print the sent parameters? Or will `wmt_cmd_sync()` log something?
> + 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 = 0xF1;
Ditto.
> + 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)
> +{
> + struct btmtk_patch_header *hdr;
> + struct btmtk_global_desc *globaldesc;
> + struct btmtk_section_map *sectionmap;
> + const struct firmware *fw;
> + const u8 *fw_ptr;
> + u8 *cert_buf = NULL;
> + u32 section_num, section_offset, dl_size, cert_len;
> + int i, 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;
> + }
> +
> + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE) {
> + bt_dev_err(hdev, "CBMCU firmware too small (%zu bytes)", fw->size);
Please log `MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE`.
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + fw_ptr = fw->data;
> + hdr = (struct btmtk_patch_header *)fw_ptr;
> + globaldesc = (struct btmtk_global_desc *)(fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE);
> + section_num = le32_to_cpu(globaldesc->section_num);
> +
> + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
> + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num) {
> + bt_dev_err(hdev, "CBMCU firmware truncated: size=%zu, expected=%zu (section_num=%u)",
> + fw->size,
> + MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
> + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
> + section_num);
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + bt_dev_info(hdev, "CBMCU Version: 0x%04x%04x, Build Time: %s",
> + le16_to_cpu(hdr->hwver), 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 +
> + 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);
> + dl_size = le32_to_cpu(sectionmap->secsize);
> +
> + if (dl_size == 0)
> + continue;
> +
> + if (section_offset > fw->size ||
> + dl_size > fw->size - section_offset) {
> + bt_dev_err(hdev, "CBMCU Phase 1 section out of bounds");
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + cert_len = MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num +
> + 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;
> +
> + cert_buf = kmalloc(cert_len, GFP_KERNEL);
> + if (!cert_buf) {
> + err = -ENOMEM;
> + goto err_release_fw;
> + }
> +
> + /* Copy Global Descriptor + All Section Maps */
> + memcpy(cert_buf,
> + fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE,
> + MTK_FW_ROM_PATCH_GD_SIZE + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num);
> +
> + /* Copy Phase 1 section data */
> + memcpy(cert_buf + MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
> + fw_ptr + section_offset,
> + dl_size);
> +
> + /* Download Phase 1 section */
> + err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync,
> + cert_buf, cert_len);
> + kfree(cert_buf);
> + cert_buf = NULL;
> +
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to download CBMCU Phase 1 section (%d)", err);
> + goto err_release_fw;
> + }
> +
> + break;
> + }
> +
> + /* 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 +
> + 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;
> +
> + if (section_offset > fw->size ||
> + dl_size > fw->size - section_offset) {
> + bt_dev_err(hdev, "CBMCU Phase 2 section %d out of bounds", i);
> + err = -EINVAL;
> + 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;
> +
> + /* 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 %d (%d)", i, err);
> + goto err_release_fw;
> + }
> + }
> +
> + /* Wait for firmware activation */
> + usleep_range(100000, 120000);
Does the firmware really take this long? (Please report to the firmware
engineers to optimize this. Optimized Linux takes less time. ;-)) Isn’t
there a way to poll the readiness?
> +
> + bt_dev_info(hdev, "CBMCU firmware download completed");
For me, Linux uploads firmware to the device (and the BT device would
download it). But this is not a BT device message but the OS message?
Feel free to ignore.
> +
> +err_release_fw:
> + 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);
> +
> + if (patch_status != BTMTK_WMT_PATCH_UNDONE)
> + return 0;
> +
> + snprintf(cbmcu_fwname, sizeof(cbmcu_fwname),
> + "mediatek/mt7928/CBMCU_CODE_MT%04x_1_1.bin",
> + dev_id & 0xffff);
> +
> + err = btmtk_load_cbmcu_firmware(hdev, cbmcu_fwname, wmt_cmd_sync);
> + 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)
> {
> u32 val;
> @@ -894,7 +1229,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
> if (err < 0)
> return err;
> msleep(100);
> - } else if (dev_id == 0x7925 || dev_id == 0x6639) {
> + } else if (dev_id == 0x7925 || dev_id == 0x6639 || dev_id == 0x7935) {
This should be sorted in my opinion. Maybe add a commit before, and put
the new id at the beginning.
> err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val);
> if (err < 0)
> return err;
> @@ -1379,6 +1714,15 @@ 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);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to set up CBMCU firmware (%d)", err);
> + return err;
> + }
> + fallthrough;
> case 0x7922:
> case 0x7925:
> /*
> @@ -1596,3 +1940,5 @@ MODULE_FIRMWARE(FIRMWARE_MT7922);
> MODULE_FIRMWARE(FIRMWARE_MT7961);
> 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 c83c24897c95..6d3bf6b74a1d 100644
> --- a/drivers/bluetooth/btmtk.h
> +++ b/drivers/bluetooth/btmtk.h
> @@ -9,6 +9,8 @@
> #define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_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
> @@ -54,6 +56,7 @@ enum {
> BTMTK_WMT_RST = 0x7,
> BTMTK_WMT_REGISTER = 0x8,
> BTMTK_WMT_SEMAPHORE = 0x17,
> + BTMTK_WMT_CBMCU_DWNLD = 0x58,
> };
>
> enum {
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v3] Bluetooth: btmtk: Add MT7928 support
@ 2026-06-17 23:51 Chris Lu
2026-06-18 5:30 ` Paul Menzel
0 siblings, 1 reply; 4+ messages in thread
From: Chris Lu @ 2026-06-17 23:51 UTC (permalink / raw)
To: Marcel Holtmann, Johan Hedberg, Luiz Von Dentz
Cc: Sean Wang, Will Lee, SS Wu, Steve Lee, linux-bluetooth,
linux-kernel, linux-mediatek, Chris Lu
Add support for MT7928 (internal device ID is MT7935) which
requires additional firmware (CBMCU firmware) loading before
Bluetooth firmware.
CBMCU is a new component on MT7928 to handle common part shared
across the combo chip (Wi-Fi/Bluetooth's subsystem), providing
a better user experience through improved coordination between
subsystems.
Implement two-phase CBMCU firmware download: Phase 1 loads
section with type 0x5 containing global descriptor,
section maps and signature data; Phase 2 loads remaining
firmware sections. Add retry mechanism for concurrent download
protection.
After CBMCU firmware loads successfully, the driver continues
to load corresponding BT firmware based on device ID through
fallthrough to case 0x7922/0x7925.
The firmware required for MT7928 will be scheduled for upload
to linux-firmware at a later stage.
MT7928 bringup kernel log:
[90.209995] usb 1-3: New USB device found, idVendor=0e8d,
idProduct=7935, bcdDevice= 1.00
[90.210027] usb 1-3: New USB device strings: Mfr=5,
Product=6, SerialNumber=7
[90.210046] usb 1-3: Product: Wireless_Device
[90.210060] usb 1-3: Manufacturer: MediaTek Inc.
[90.210075] usb 1-3: SerialNumber: 000000000
[90.223089] Bluetooth: hci1: CBMCU Version: 0x00000000,
Build Time: 20260601T161751+0800
[90.664706] Bluetooth: hci1: CBMCU firmware download completed
[90.685424] Bluetooth: hci1: HW/SW Version: 0x00000000,
Build Time: 20260527000816
[93.771612] Bluetooth: hci1: Device setup in 3467323 usecs
[93.771657] Bluetooth: hci1: HCI Enhanced Setup Synchronous
Connection command is advertised, but not supported.
[93.890840] Bluetooth: hci1: AOSP extensions version v2.00
[93.890887] Bluetooth: hci1: AOSP quality report is supported
[93.893444] Bluetooth: MGMT ver 1.23
Signed-off-by: Chris Lu <chris.lu@mediatek.com>
---
v1->v2: Update error message; Use macro instead of magic number.
v2->v3: Update commit message.
---
drivers/bluetooth/btmtk.c | 348 +++++++++++++++++++++++++++++++++++++-
drivers/bluetooth/btmtk.h | 3 +
2 files changed, 350 insertions(+), 1 deletion(-)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index 02a96342e964..6bae0b0794dd 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c
@@ -21,6 +21,8 @@
#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
/* It is for mt79xx iso data transmission setting */
#define MTK_ISO_THRESHOLD 264
@@ -120,6 +122,10 @@ 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);
+ 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",
@@ -734,6 +740,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)
@@ -870,6 +877,334 @@ 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, err, retry = 20;
+
+ do {
+ wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
+ wmt_params.flag = 0xF0;
+ 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);
+
+ return 0;
+}
+
+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, err;
+
+ 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;
+
+ err = wmt_cmd_sync(hdev, &wmt_params);
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to query CBMCU section (%d)", err);
+ return err;
+ }
+
+ /* Query should return UNDONE status for successful section query */
+ if (status != BTMTK_WMT_PATCH_UNDONE) {
+ bt_dev_err(hdev, "CBMCU section query status error (%d)", status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+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 = 1;
+ else if (dl_size == sent_len)
+ wmt_params.flag = 3;
+ else
+ wmt_params.flag = 2;
+
+ 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 = 0xF1;
+ 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)
+{
+ struct btmtk_patch_header *hdr;
+ struct btmtk_global_desc *globaldesc;
+ struct btmtk_section_map *sectionmap;
+ const struct firmware *fw;
+ const u8 *fw_ptr;
+ u8 *cert_buf = NULL;
+ u32 section_num, section_offset, dl_size, cert_len;
+ int i, 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;
+ }
+
+ if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE) {
+ bt_dev_err(hdev, "CBMCU firmware too small (%zu bytes)", fw->size);
+ err = -EINVAL;
+ goto err_release_fw;
+ }
+
+ fw_ptr = fw->data;
+ hdr = (struct btmtk_patch_header *)fw_ptr;
+ globaldesc = (struct btmtk_global_desc *)(fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE);
+ section_num = le32_to_cpu(globaldesc->section_num);
+
+ if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
+ (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num) {
+ bt_dev_err(hdev, "CBMCU firmware truncated: size=%zu, expected=%zu (section_num=%u)",
+ fw->size,
+ MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
+ (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
+ section_num);
+ err = -EINVAL;
+ goto err_release_fw;
+ }
+
+ bt_dev_info(hdev, "CBMCU Version: 0x%04x%04x, Build Time: %s",
+ le16_to_cpu(hdr->hwver), 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 +
+ 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);
+ dl_size = le32_to_cpu(sectionmap->secsize);
+
+ if (dl_size == 0)
+ continue;
+
+ if (section_offset > fw->size ||
+ dl_size > fw->size - section_offset) {
+ bt_dev_err(hdev, "CBMCU Phase 1 section out of bounds");
+ err = -EINVAL;
+ goto err_release_fw;
+ }
+
+ cert_len = MTK_FW_ROM_PATCH_GD_SIZE +
+ MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num +
+ 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;
+
+ cert_buf = kmalloc(cert_len, GFP_KERNEL);
+ if (!cert_buf) {
+ err = -ENOMEM;
+ goto err_release_fw;
+ }
+
+ /* Copy Global Descriptor + All Section Maps */
+ memcpy(cert_buf,
+ fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE,
+ MTK_FW_ROM_PATCH_GD_SIZE + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num);
+
+ /* Copy Phase 1 section data */
+ memcpy(cert_buf + MTK_FW_ROM_PATCH_GD_SIZE +
+ MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
+ fw_ptr + section_offset,
+ dl_size);
+
+ /* Download Phase 1 section */
+ err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync,
+ cert_buf, cert_len);
+ kfree(cert_buf);
+ cert_buf = NULL;
+
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to download CBMCU Phase 1 section (%d)", err);
+ goto err_release_fw;
+ }
+
+ break;
+ }
+
+ /* 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 +
+ 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;
+
+ if (section_offset > fw->size ||
+ dl_size > fw->size - section_offset) {
+ bt_dev_err(hdev, "CBMCU Phase 2 section %d out of bounds", i);
+ err = -EINVAL;
+ 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;
+
+ /* 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 %d (%d)", i, err);
+ goto err_release_fw;
+ }
+ }
+
+ /* Wait for firmware activation */
+ usleep_range(100000, 120000);
+
+ bt_dev_info(hdev, "CBMCU firmware download completed");
+
+err_release_fw:
+ 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);
+
+ if (patch_status != BTMTK_WMT_PATCH_UNDONE)
+ return 0;
+
+ snprintf(cbmcu_fwname, sizeof(cbmcu_fwname),
+ "mediatek/mt7928/CBMCU_CODE_MT%04x_1_1.bin",
+ dev_id & 0xffff);
+
+ err = btmtk_load_cbmcu_firmware(hdev, cbmcu_fwname, wmt_cmd_sync);
+ 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)
{
u32 val;
@@ -894,7 +1229,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
if (err < 0)
return err;
msleep(100);
- } else if (dev_id == 0x7925 || dev_id == 0x6639) {
+ } else if (dev_id == 0x7925 || dev_id == 0x6639 || dev_id == 0x7935) {
err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val);
if (err < 0)
return err;
@@ -1379,6 +1714,15 @@ 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);
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to set up CBMCU firmware (%d)", err);
+ return err;
+ }
+ fallthrough;
case 0x7922:
case 0x7925:
/*
@@ -1596,3 +1940,5 @@ MODULE_FIRMWARE(FIRMWARE_MT7922);
MODULE_FIRMWARE(FIRMWARE_MT7961);
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 c83c24897c95..6d3bf6b74a1d 100644
--- a/drivers/bluetooth/btmtk.h
+++ b/drivers/bluetooth/btmtk.h
@@ -9,6 +9,8 @@
#define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_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
@@ -54,6 +56,7 @@ enum {
BTMTK_WMT_RST = 0x7,
BTMTK_WMT_REGISTER = 0x8,
BTMTK_WMT_SEMAPHORE = 0x17,
+ BTMTK_WMT_CBMCU_DWNLD = 0x58,
};
enum {
--
2.45.2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-06 3:23 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-06 3:23 [PATCH v3] Bluetooth: btmtk: Add MT7928 support Chris Lu
-- strict thread matches above, loose matches on Subject: below --
2026-06-17 23:51 Chris Lu
2026-06-18 5:30 ` Paul Menzel
2026-06-22 5:32 ` Chris Lu (陸稚泓)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®