Bluetooth: btusb: Add protocol support for MediaTek MT7921U USB devices
authorMark Chen <Mark-YW.Chen@mediatek.com>
Tue, 2 Feb 2021 10:26:18 +0000 (18:26 +0800)
committerMarcel Holtmann <marcel@holtmann.org>
Tue, 2 Feb 2021 16:21:52 +0000 (17:21 +0100)
There is mt7921 firmware download mechanism

1. Read Chip id from MT7921.

2. Download firmware by endpoint 0, it's the same mechanism with
mt7663/mt7668.
   (it's medaitek specific header format for downloading firmware.)

3. Enabling Bluetooth function.

The information in /sys/kernel/debug/usb/devices about the MT7921U
Bluetooth device is listed as the below.

  T:  Bus=01 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 40 Spd=480  MxCh= 0
  D:  Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs=  1
  P:  Vendor=0e8d ProdID=7961 Rev= 1.00
  S:  Manufacturer=MediaTek Inc.
  S:  Product=Wireless_Device
  S:  SerialNumber=000000000
  C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA
  A:  FirstIf#= 0 IfCount= 3 Cls=e0(wlcon) Sub=01 Prot=01
  I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=81(I) Atr=03(Int.) MxPS=  16 Ivl=125us
  E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
  E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
  I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=   0 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=   0 Ivl=1ms
  I:  If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=   9 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=   9 Ivl=1ms
  I:  If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=  17 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=  17 Ivl=1ms
  I:  If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=  25 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=  25 Ivl=1ms
  I:  If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=  33 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=  33 Ivl=1ms
  I:  If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=  49 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=  49 Ivl=1ms
  I:  If#= 1 Alt= 6 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
  E:  Ad=83(I) Atr=01(Isoc) MxPS=  63 Ivl=1ms
  E:  Ad=03(O) Atr=01(Isoc) MxPS=  63 Ivl=1ms
  I:* If#= 2 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
  E:  Ad=8a(I) Atr=03(Int.) MxPS=  64 Ivl=125us
  E:  Ad=0a(O) Atr=03(Int.) MxPS=  64 Ivl=125us
  I:  If#= 2 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=(none)
  E:  Ad=8a(I) Atr=03(Int.) MxPS= 512 Ivl=125us
  E:  Ad=0a(O) Atr=03(Int.) MxPS= 512 Ivl=125us

Signed-off-by: Mark Chen <Mark-YW.Chen@mediatek.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
drivers/bluetooth/btusb.c

index fd33a31..eeafb84 100644 (file)
@@ -3128,6 +3128,12 @@ static int btusb_shutdown_intel_new(struct hci_dev *hdev)
 #define FIRMWARE_MT7668                "mediatek/mt7668pr2h.bin"
 
 #define HCI_WMT_MAX_EVENT_SIZE         64
+/* It is for mt79xx download rom patch*/
+#define MTK_FW_ROM_PATCH_HEADER_SIZE   32
+#define MTK_FW_ROM_PATCH_GD_SIZE       64
+#define MTK_FW_ROM_PATCH_SEC_MAP_SIZE  64
+#define MTK_SEC_MAP_COMMON_SIZE        12
+#define MTK_SEC_MAP_NEED_SEND_SIZE     52
 
 enum {
        BTMTK_WMT_PATCH_DWNLD = 0x1,
@@ -3184,6 +3190,40 @@ struct btmtk_hci_wmt_params {
        u32 *status;
 };
 
+struct btmtk_patch_header {
+       u8 datetime[16];
+       u8 platform[4];
+       __le16 hwver;
+       __le16 swver;
+       __le32 magicnum;
+} __packed;
+
+struct btmtk_global_desc {
+       __le32 patch_ver;
+       __le32 sub_sys;
+       __le32 feature_opt;
+       __le32 section_num;
+} __packed;
+
+struct btmtk_section_map {
+       __le32 sectype;
+       __le32 secoffset;
+       __le32 secsize;
+       union {
+               __le32 u4SecSpec[13];
+               struct {
+                       __le32 dlAddr;
+                       __le32 dlsize;
+                       __le32 seckeyidx;
+                       __le32 alignlen;
+                       __le32 sectype;
+                       __le32 dlmodecrctype;
+                       __le32 crc;
+                       __le32 reserved[6];
+               } bin_info_spec;
+       };
+} __packed;
+
 static void btusb_mtk_wmt_recv(struct urb *urb)
 {
        struct hci_dev *hdev = urb->context;
@@ -3407,6 +3447,14 @@ static int btusb_mtk_hci_wmt_sync(struct hci_dev *hdev,
                else
                        status = BTMTK_WMT_ON_UNDONE;
                break;
+       case BTMTK_WMT_PATCH_DWNLD:
+               if (wmt_evt->whdr.flag == 2)
+                       status = BTMTK_WMT_PATCH_DONE;
+               else if (wmt_evt->whdr.flag == 1)
+                       status = BTMTK_WMT_PATCH_PROGRESS;
+               else
+                       status = BTMTK_WMT_PATCH_UNDONE;
+               break;
        }
 
        if (wmt_params->status)
@@ -3419,6 +3467,122 @@ err_free_skb:
        return err;
 }
 
+static int btusb_mtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname)
+{
+       struct btmtk_hci_wmt_params wmt_params;
+       struct btmtk_patch_header *patchhdr = NULL;
+       struct btmtk_global_desc *globaldesc = NULL;
+       struct btmtk_section_map *sectionmap;
+       const struct firmware *fw;
+       const u8 *fw_ptr;
+       const u8 *fw_bin_ptr;
+       size_t fw_size;
+       int err, dlen, i, status;
+       u8 flag, first_block, retry;
+       u32 section_num, dl_size, section_offset;
+       u8 cmd[64];
+
+       err = request_firmware(&fw, fwname, &hdev->dev);
+       if (err < 0) {
+               bt_dev_err(hdev, "Failed to load firmware file (%d)", err);
+               return err;
+       }
+
+       fw_ptr = fw->data;
+       fw_bin_ptr = fw_ptr;
+       fw_size = fw->size;
+       patchhdr = (struct btmtk_patch_header *)fw_ptr;
+       globaldesc = (struct btmtk_global_desc *)(fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE);
+       section_num = globaldesc->section_num;
+
+       for (i = 0; i < section_num; i++) {
+               first_block = 1;
+               fw_ptr = fw_bin_ptr;
+               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);
+
+               section_offset = sectionmap->secoffset;
+               dl_size = sectionmap->bin_info_spec.dlsize;
+
+               if (dl_size > 0) {
+                       retry = 20;
+                       while (retry > 0) {
+                               cmd[0] = 0; /* 0 means legacy dl mode. */
+                               memcpy(cmd + 1,
+                                      fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE +
+                                      MTK_FW_ROM_PATCH_GD_SIZE + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i +
+                                      MTK_SEC_MAP_COMMON_SIZE,
+                                      MTK_SEC_MAP_NEED_SEND_SIZE + 1);
+
+                               wmt_params.op = BTMTK_WMT_PATCH_DWNLD;
+                               wmt_params.status = &status;
+                               wmt_params.flag = 0;
+                               wmt_params.dlen = MTK_SEC_MAP_NEED_SEND_SIZE + 1;
+                               wmt_params.data = &cmd;
+
+                               err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+                               if (err < 0) {
+                                       bt_dev_err(hdev, "Failed to send wmt patch dwnld (%d)",
+                                                  err);
+                                       goto err_release_fw;
+                               }
+
+                               if (status == BTMTK_WMT_PATCH_UNDONE) {
+                                       break;
+                               } else if (status == BTMTK_WMT_PATCH_PROGRESS) {
+                                       msleep(100);
+                                       retry--;
+                               } else if (status == BTMTK_WMT_PATCH_DONE) {
+                                       goto next_section;
+                               } else {
+                                       bt_dev_err(hdev, "Failed wmt patch dwnld status (%d)",
+                                                  status);
+                                       goto err_release_fw;
+                               }
+                       }
+
+                       fw_ptr += section_offset;
+                       wmt_params.op = BTMTK_WMT_PATCH_DWNLD;
+                       wmt_params.status = NULL;
+
+                       while (dl_size > 0) {
+                               dlen = min_t(int, 250, dl_size);
+                               if (first_block == 1) {
+                                       flag = 1;
+                                       first_block = 0;
+                               } else if (dl_size - dlen <= 0) {
+                                       flag = 3;
+                               } else {
+                                       flag = 2;
+                               }
+
+                               wmt_params.flag = flag;
+                               wmt_params.dlen = dlen;
+                               wmt_params.data = fw_ptr;
+
+                               err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+                               if (err < 0) {
+                                       bt_dev_err(hdev, "Failed to send wmt patch dwnld (%d)",
+                                                  err);
+                                       goto err_release_fw;
+                               }
+
+                               dl_size -= dlen;
+                               fw_ptr += dlen;
+                       }
+               }
+next_section:
+               continue;
+       }
+       /* Wait a few moments for firmware activation done */
+       usleep_range(100000, 120000);
+
+err_release_fw:
+       release_firmware(fw);
+
+       return err;
+}
+
 static int btusb_mtk_setup_firmware(struct hci_dev *hdev, const char *fwname)
 {
        struct btmtk_hci_wmt_params wmt_params;
@@ -3573,6 +3737,8 @@ static int btusb_mtk_setup(struct hci_dev *hdev)
        const char *fwname;
        int err, status;
        u32 dev_id;
+       char fw_bin_name[64];
+       u32 fw_version;
        u8 param;
 
        calltime = ktime_get();
@@ -3583,6 +3749,19 @@ static int btusb_mtk_setup(struct hci_dev *hdev)
                return err;
        }
 
+       if (!dev_id) {
+               err = btusb_mtk_id_get(data, 0x70010200, &dev_id);
+               if (err < 0) {
+                       bt_dev_err(hdev, "Failed to get device id (%d)", err);
+                       return err;
+               }
+               err = btusb_mtk_id_get(data, 0x80021004, &fw_version);
+               if (err < 0) {
+                       bt_dev_err(hdev, "Failed to get fw version (%d)", err);
+                       return err;
+               }
+       }
+
        switch (dev_id) {
        case 0x7663:
                fwname = FIRMWARE_MT7663;
@@ -3590,6 +3769,26 @@ static int btusb_mtk_setup(struct hci_dev *hdev)
        case 0x7668:
                fwname = FIRMWARE_MT7668;
                break;
+       case 0x7961:
+               snprintf(fw_bin_name, sizeof(fw_bin_name),
+                       "mediatek/BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+                        dev_id & 0xffff, (fw_version & 0xff) + 1);
+               err = btusb_mtk_setup_firmware_79xx(hdev, fw_bin_name);
+
+               /* Enable Bluetooth protocol */
+               param = 1;
+               wmt_params.op = BTMTK_WMT_FUNC_CTRL;
+               wmt_params.flag = 0;
+               wmt_params.dlen = sizeof(param);
+               wmt_params.data = &param;
+               wmt_params.status = NULL;
+
+               err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+               if (err < 0) {
+                       bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err);
+                       return err;
+               }
+               goto done;
        default:
                bt_dev_err(hdev, "Unsupported support hardware variant (%08x)",
                           dev_id);
@@ -3667,6 +3866,7 @@ ignore_func_on:
        }
        kfree_skb(skb);
 
+done:
        rettime = ktime_get();
        delta = ktime_sub(rettime, calltime);
        duration = (unsigned long long)ktime_to_ns(delta) >> 10;