Bluetooth: btusb: Add protocol support for MediaTek MT7668U USB devices
authorSean Wang <sean.wang@mediatek.com>
Sun, 2 Jun 2019 00:02:48 +0000 (08:02 +0800)
committerMarcel Holtmann <marcel@holtmann.org>
Sat, 6 Jul 2019 19:44:25 +0000 (21:44 +0200)
This adds the support of enabling MT7668U Bluetooth function running
on the top of btusb driver.

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

T:  Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#=  2 Spd=5000 MxCh= 0
D:  Ver= 3.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS= 9 #Cfgs=  1
P:  Vendor=0e8d ProdID=7668 Rev= 1.00
S:  Manufacturer=MediaTek Inc.
S:  Product=Wireless_Device
S:  SerialNumber=000000000
C:* #Ifs= 3 Cfg#= 1 Atr=a0 MxPwr=160mA
A:  FirstIf#= 0 IfCount= 2 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=1024 Ivl=0ms
E:  Ad=02(O) Atr=02(Bulk) MxPS=1024 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

Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
drivers/bluetooth/Kconfig
drivers/bluetooth/btusb.c

index a3fafd781aa1c0d94ead60f411ed334fa0d7e822..aae665a3a254e32a11c885be0394bcdeb1c3a5d9 100644 (file)
@@ -52,6 +52,17 @@ config BT_HCIBTUSB_BCM
 
          Say Y here to compile support for Broadcom protocol.
 
+config BT_HCIBTUSB_MTK
+       bool "MediaTek protocol support"
+       depends on BT_HCIBTUSB
+       default n
+       help
+         The MediaTek protocol support enables firmware download
+         support and chip initialization for MediaTek Bluetooth
+         USB controllers.
+
+         Say Y here to compile support for MediaTek protocol.
+
 config BT_HCIBTUSB_RTL
        bool "Realtek protocol support"
        depends on BT_HCIBTUSB
index c550a8e7c3360f0901ebc46ae39424c08e4b322c..0c58fd1a5b0f0c4cab314de80a1c2839db951333 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/usb.h>
 #include <linux/usb/quirks.h>
 #include <linux/firmware.h>
+#include <linux/iopoll.h>
 #include <linux/of_device.h>
 #include <linux/of_irq.h>
 #include <linux/suspend.h>
@@ -55,6 +56,7 @@ static struct usb_driver btusb_driver;
 #define BTUSB_BCM2045          0x40000
 #define BTUSB_IFNUM_2          0x80000
 #define BTUSB_CW6622           0x100000
+#define BTUSB_MEDIATEK         0x200000
 
 static const struct usb_device_id btusb_table[] = {
        /* Generic Bluetooth USB device */
@@ -348,6 +350,10 @@ static const struct usb_device_id blacklist_table[] = {
        { USB_VENDOR_AND_INTERFACE_INFO(0x0bda, 0xe0, 0x01, 0x01),
          .driver_info = BTUSB_REALTEK },
 
+       /* MediaTek Bluetooth devices */
+       { USB_VENDOR_AND_INTERFACE_INFO(0x0e8d, 0xe0, 0x01, 0x01),
+         .driver_info = BTUSB_MEDIATEK },
+
        /* Additional Realtek 8723AE Bluetooth devices */
        { USB_DEVICE(0x0930, 0x021d), .driver_info = BTUSB_REALTEK },
        { USB_DEVICE(0x13d3, 0x3394), .driver_info = BTUSB_REALTEK },
@@ -428,6 +434,7 @@ static const struct dmi_system_id btusb_needs_reset_resume_table[] = {
 #define BTUSB_DIAG_RUNNING     10
 #define BTUSB_OOB_WAKE_ENABLED 11
 #define BTUSB_HW_RESET_ACTIVE  12
+#define BTUSB_TX_WAIT_VND_EVT  13
 
 struct btusb_data {
        struct hci_dev       *hdev;
@@ -451,6 +458,7 @@ struct btusb_data {
        struct usb_anchor bulk_anchor;
        struct usb_anchor isoc_anchor;
        struct usb_anchor diag_anchor;
+       struct usb_anchor ctrl_anchor;
        spinlock_t rxlock;
 
        struct sk_buff *evt_skb;
@@ -1204,6 +1212,7 @@ static void btusb_stop_traffic(struct btusb_data *data)
        usb_kill_anchored_urbs(&data->bulk_anchor);
        usb_kill_anchored_urbs(&data->isoc_anchor);
        usb_kill_anchored_urbs(&data->diag_anchor);
+       usb_kill_anchored_urbs(&data->ctrl_anchor);
 }
 
 static int btusb_close(struct hci_dev *hdev)
@@ -2439,6 +2448,563 @@ static int btusb_shutdown_intel_new(struct hci_dev *hdev)
        return 0;
 }
 
+#ifdef CONFIG_BT_HCIBTUSB_MTK
+
+#define FIRMWARE_MT7668                "mediatek/mt7668pr2h.bin"
+
+#define HCI_WMT_MAX_EVENT_SIZE         64
+
+enum {
+       BTMTK_WMT_PATCH_DWNLD = 0x1,
+       BTMTK_WMT_FUNC_CTRL = 0x6,
+       BTMTK_WMT_RST = 0x7,
+       BTMTK_WMT_SEMAPHORE = 0x17,
+};
+
+enum {
+       BTMTK_WMT_INVALID,
+       BTMTK_WMT_PATCH_UNDONE,
+       BTMTK_WMT_PATCH_DONE,
+       BTMTK_WMT_ON_UNDONE,
+       BTMTK_WMT_ON_DONE,
+       BTMTK_WMT_ON_PROGRESS,
+};
+
+struct btmtk_wmt_hdr {
+       u8      dir;
+       u8      op;
+       __le16  dlen;
+       u8      flag;
+} __packed;
+
+struct btmtk_hci_wmt_cmd {
+       struct btmtk_wmt_hdr hdr;
+       u8 data[256];
+} __packed;
+
+struct btmtk_hci_wmt_evt {
+       struct hci_event_hdr hhdr;
+       struct btmtk_wmt_hdr whdr;
+} __packed;
+
+struct btmtk_hci_wmt_evt_funcc {
+       struct btmtk_hci_wmt_evt hwhdr;
+       __be16 status;
+} __packed;
+
+struct btmtk_tci_sleep {
+       u8 mode;
+       __le16 duration;
+       __le16 host_duration;
+       u8 host_wakeup_pin;
+       u8 time_compensation;
+} __packed;
+
+struct btmtk_hci_wmt_params {
+       u8 op;
+       u8 flag;
+       u16 dlen;
+       const void *data;
+       u32 *status;
+};
+
+static void btusb_mtk_wmt_recv(struct urb *urb)
+{
+       struct hci_dev *hdev = urb->context;
+       struct btusb_data *data = hci_get_drvdata(hdev);
+       struct hci_event_hdr *hdr;
+       struct sk_buff *skb;
+       int err;
+
+       if (urb->status == 0 && urb->actual_length > 0) {
+               hdev->stat.byte_rx += urb->actual_length;
+
+               /* WMT event shouldn't be fragmented and the size should be
+                * less than HCI_WMT_MAX_EVENT_SIZE.
+                */
+               skb = bt_skb_alloc(HCI_WMT_MAX_EVENT_SIZE, GFP_ATOMIC);
+               if (!skb) {
+                       hdev->stat.err_rx++;
+                       goto err_out;
+               }
+
+               hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
+               skb_put_data(skb, urb->transfer_buffer, urb->actual_length);
+
+               hdr = (void *)skb->data;
+               /* Fix up the vendor event id with 0xff for vendor specific
+                * instead of 0xe4 so that event send via monitoring socket can
+                * be parsed properly.
+                */
+               hdr->evt = 0xff;
+
+               /* When someone waits for the WMT event, the skb is being cloned
+                * and being processed the events from there then.
+                */
+               if (test_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags)) {
+                       data->evt_skb = skb_clone(skb, GFP_KERNEL);
+                       if (!data->evt_skb)
+                               goto err_out;
+               }
+
+               err = hci_recv_frame(hdev, skb);
+               if (err < 0)
+                       goto err_free_skb;
+
+               if (test_and_clear_bit(BTUSB_TX_WAIT_VND_EVT,
+                                      &data->flags)) {
+                       /* Barrier to sync with other CPUs */
+                       smp_mb__after_atomic();
+                       wake_up_bit(&data->flags,
+                                   BTUSB_TX_WAIT_VND_EVT);
+               }
+err_out:
+               return;
+err_free_skb:
+               kfree_skb(data->evt_skb);
+               data->evt_skb = NULL;
+               return;
+       } else if (urb->status == -ENOENT) {
+               /* Avoid suspend failed when usb_kill_urb */
+               return;
+       }
+
+       usb_mark_last_busy(data->udev);
+
+       /* The URB complete handler is still called with urb->actual_length = 0
+        * when the event is not available, so we should keep re-submitting
+        * URB until WMT event returns, Also, It's necessary to wait some time
+        * between the two consecutive control URBs to relax the target device
+        * to generate the event. Otherwise, the WMT event cannot return from
+        * the device successfully.
+        */
+       udelay(100);
+
+       usb_anchor_urb(urb, &data->ctrl_anchor);
+       err = usb_submit_urb(urb, GFP_ATOMIC);
+       if (err < 0) {
+               /* -EPERM: urb is being killed;
+                * -ENODEV: device got disconnected
+                */
+               if (err != -EPERM && err != -ENODEV)
+                       bt_dev_err(hdev, "urb %p failed to resubmit (%d)",
+                                  urb, -err);
+               usb_unanchor_urb(urb);
+       }
+}
+
+static int btusb_mtk_submit_wmt_recv_urb(struct hci_dev *hdev)
+{
+       struct btusb_data *data = hci_get_drvdata(hdev);
+       struct usb_ctrlrequest *dr;
+       unsigned char *buf;
+       int err, size = 64;
+       unsigned int pipe;
+       struct urb *urb;
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!urb)
+               return -ENOMEM;
+
+       dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+       if (!dr) {
+               usb_free_urb(urb);
+               return -ENOMEM;
+       }
+
+       dr->bRequestType = USB_TYPE_VENDOR | USB_DIR_IN;
+       dr->bRequest     = 1;
+       dr->wIndex       = cpu_to_le16(0);
+       dr->wValue       = cpu_to_le16(48);
+       dr->wLength      = cpu_to_le16(size);
+
+       buf = kmalloc(size, GFP_KERNEL);
+       if (!buf) {
+               kfree(dr);
+               return -ENOMEM;
+       }
+
+       pipe = usb_rcvctrlpipe(data->udev, 0);
+
+       usb_fill_control_urb(urb, data->udev, pipe, (void *)dr,
+                            buf, size, btusb_mtk_wmt_recv, hdev);
+
+       urb->transfer_flags |= URB_FREE_BUFFER;
+
+       usb_anchor_urb(urb, &data->ctrl_anchor);
+       err = usb_submit_urb(urb, GFP_KERNEL);
+       if (err < 0) {
+               if (err != -EPERM && err != -ENODEV)
+                       bt_dev_err(hdev, "urb %p submission failed (%d)",
+                                  urb, -err);
+               usb_unanchor_urb(urb);
+       }
+
+       usb_free_urb(urb);
+
+       return err;
+}
+
+static int btusb_mtk_hci_wmt_sync(struct hci_dev *hdev,
+                                 struct btmtk_hci_wmt_params *wmt_params)
+{
+       struct btusb_data *data = hci_get_drvdata(hdev);
+       struct btmtk_hci_wmt_evt_funcc *wmt_evt_funcc;
+       u32 hlen, status = BTMTK_WMT_INVALID;
+       struct btmtk_hci_wmt_evt *wmt_evt;
+       struct btmtk_hci_wmt_cmd wc;
+       struct btmtk_wmt_hdr *hdr;
+       int err;
+
+       /* Submit control IN URB on demand to process the WMT event */
+       err = btusb_mtk_submit_wmt_recv_urb(hdev);
+       if (err < 0)
+               return err;
+
+       /* Send the WMT command and wait until the WMT event returns */
+       hlen = sizeof(*hdr) + wmt_params->dlen;
+       if (hlen > 255)
+               return -EINVAL;
+
+       hdr = (struct btmtk_wmt_hdr *)&wc;
+       hdr->dir = 1;
+       hdr->op = wmt_params->op;
+       hdr->dlen = cpu_to_le16(wmt_params->dlen + 1);
+       hdr->flag = wmt_params->flag;
+       memcpy(wc.data, wmt_params->data, wmt_params->dlen);
+
+       set_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags);
+
+       err = __hci_cmd_send(hdev, 0xfc6f, hlen, &wc);
+
+       if (err < 0) {
+               clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags);
+               return err;
+       }
+
+       /* The vendor specific WMT commands are all answered by a vendor
+        * specific event and will have the Command Status or Command
+        * Complete as with usual HCI command flow control.
+        *
+        * After sending the command, wait for BTUSB_TX_WAIT_VND_EVT
+        * state to be cleared. The driver specific event receive routine
+        * will clear that state and with that indicate completion of the
+        * WMT command.
+        */
+       err = wait_on_bit_timeout(&data->flags, BTUSB_TX_WAIT_VND_EVT,
+                                 TASK_INTERRUPTIBLE, HCI_INIT_TIMEOUT);
+       if (err == -EINTR) {
+               bt_dev_err(hdev, "Execution of wmt command interrupted");
+               clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags);
+               return err;
+       }
+
+       if (err) {
+               bt_dev_err(hdev, "Execution of wmt command timed out");
+               clear_bit(BTUSB_TX_WAIT_VND_EVT, &data->flags);
+               return -ETIMEDOUT;
+       }
+
+       /* Parse and handle the return WMT event */
+       wmt_evt = (struct btmtk_hci_wmt_evt *)data->evt_skb->data;
+       if (wmt_evt->whdr.op != hdr->op) {
+               bt_dev_err(hdev, "Wrong op received %d expected %d",
+                          wmt_evt->whdr.op, hdr->op);
+               err = -EIO;
+               goto err_free_skb;
+       }
+
+       switch (wmt_evt->whdr.op) {
+       case BTMTK_WMT_SEMAPHORE:
+               if (wmt_evt->whdr.flag == 2)
+                       status = BTMTK_WMT_PATCH_UNDONE;
+               else
+                       status = BTMTK_WMT_PATCH_DONE;
+               break;
+       case BTMTK_WMT_FUNC_CTRL:
+               wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt;
+               if (be16_to_cpu(wmt_evt_funcc->status) == 0x404)
+                       status = BTMTK_WMT_ON_DONE;
+               else if (be16_to_cpu(wmt_evt_funcc->status) == 0x420)
+                       status = BTMTK_WMT_ON_PROGRESS;
+               else
+                       status = BTMTK_WMT_ON_UNDONE;
+               break;
+       }
+
+       if (wmt_params->status)
+               *wmt_params->status = status;
+
+err_free_skb:
+       kfree_skb(data->evt_skb);
+       data->evt_skb = NULL;
+
+       return err;
+}
+
+static int btusb_mtk_setup_firmware(struct hci_dev *hdev, const char *fwname)
+{
+       struct btmtk_hci_wmt_params wmt_params;
+       const struct firmware *fw;
+       const u8 *fw_ptr;
+       size_t fw_size;
+       int err, dlen;
+       u8 flag;
+
+       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_size = fw->size;
+
+       /* The size of patch header is 30 bytes, should be skip */
+       if (fw_size < 30)
+               goto err_release_fw;
+
+       fw_size -= 30;
+       fw_ptr += 30;
+       flag = 1;
+
+       wmt_params.op = BTMTK_WMT_PATCH_DWNLD;
+       wmt_params.status = NULL;
+
+       while (fw_size > 0) {
+               dlen = min_t(int, 250, fw_size);
+
+               /* Tell deivice the position in sequence */
+               if (fw_size - dlen <= 0)
+                       flag = 3;
+               else if (fw_size < fw->size - 30)
+                       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;
+               }
+
+               fw_size -= dlen;
+               fw_ptr += dlen;
+       }
+
+       wmt_params.op = BTMTK_WMT_RST;
+       wmt_params.flag = 4;
+       wmt_params.dlen = 0;
+       wmt_params.data = NULL;
+       wmt_params.status = NULL;
+
+       /* Activate funciton the firmware providing to */
+       err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+       if (err < 0) {
+               bt_dev_err(hdev, "Failed to send wmt rst (%d)", err);
+               return err;
+       }
+
+       /* Wait a few moments for firmware activation done */
+       usleep_range(10000, 12000);
+
+err_release_fw:
+       release_firmware(fw);
+
+       return err;
+}
+
+static int btusb_mtk_func_query(struct hci_dev *hdev)
+{
+       struct btmtk_hci_wmt_params wmt_params;
+       int status, err;
+       u8 param = 0;
+
+       /* Query whether the function is enabled */
+       wmt_params.op = BTMTK_WMT_FUNC_CTRL;
+       wmt_params.flag = 4;
+       wmt_params.dlen = sizeof(param);
+       wmt_params.data = &param;
+       wmt_params.status = &status;
+
+       err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+       if (err < 0) {
+               bt_dev_err(hdev, "Failed to query function status (%d)", err);
+               return err;
+       }
+
+       return status;
+}
+
+static int btusb_mtk_reg_read(struct btusb_data *data, u32 reg, u32 *val)
+{
+       int pipe, err, size = sizeof(u32);
+       void *buf;
+
+       buf = kzalloc(size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       pipe = usb_rcvctrlpipe(data->udev, 0);
+       err = usb_control_msg(data->udev, pipe, 0x63,
+                             USB_TYPE_VENDOR | USB_DIR_IN,
+                             reg >> 16, reg & 0xffff,
+                             buf, size, USB_CTRL_SET_TIMEOUT);
+       if (err < 0)
+               goto err_free_buf;
+
+       *val = get_unaligned_le32(buf);
+
+err_free_buf:
+       kfree(buf);
+
+       return err;
+}
+
+static int btusb_mtk_id_get(struct btusb_data *data, u32 *id)
+{
+       return btusb_mtk_reg_read(data, 0x80000008, id);
+}
+
+static int btusb_mtk_setup(struct hci_dev *hdev)
+{
+       struct btusb_data *data = hci_get_drvdata(hdev);
+       struct btmtk_hci_wmt_params wmt_params;
+       ktime_t calltime, delta, rettime;
+       struct btmtk_tci_sleep tci_sleep;
+       unsigned long long duration;
+       struct sk_buff *skb;
+       const char *fwname;
+       int err, status;
+       u32 dev_id;
+       u8 param;
+
+       calltime = ktime_get();
+
+       err = btusb_mtk_id_get(data, &dev_id);
+       if (err < 0) {
+               bt_dev_err(hdev, "Failed to get device id (%d)", err);
+               return err;
+       }
+
+       switch (dev_id) {
+       case 0x7668:
+               fwname = FIRMWARE_MT7668;
+               break;
+       default:
+               bt_dev_err(hdev, "Unsupported support hardware variant (%08x)",
+                          dev_id);
+               return -ENODEV;
+       }
+
+       /* Query whether the firmware is already download */
+       wmt_params.op = BTMTK_WMT_SEMAPHORE;
+       wmt_params.flag = 1;
+       wmt_params.dlen = 0;
+       wmt_params.data = NULL;
+       wmt_params.status = &status;
+
+       err = btusb_mtk_hci_wmt_sync(hdev, &wmt_params);
+       if (err < 0) {
+               bt_dev_err(hdev, "Failed to query firmware status (%d)", err);
+               return err;
+       }
+
+       if (status == BTMTK_WMT_PATCH_DONE) {
+               bt_dev_info(hdev, "firmware already downloaded");
+               goto ignore_setup_fw;
+       }
+
+       /* Setup a firmware which the device definitely requires */
+       err = btusb_mtk_setup_firmware(hdev, fwname);
+       if (err < 0)
+               return err;
+
+ignore_setup_fw:
+       err = readx_poll_timeout(btusb_mtk_func_query, hdev, status,
+                                status < 0 || status != BTMTK_WMT_ON_PROGRESS,
+                                2000, 5000000);
+       /* -ETIMEDOUT happens */
+       if (err < 0)
+               return err;
+
+       /* The other errors happen in btusb_mtk_func_query */
+       if (status < 0)
+               return status;
+
+       if (status == BTMTK_WMT_ON_DONE) {
+               bt_dev_info(hdev, "function already on");
+               goto ignore_func_on;
+       }
+
+       /* 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;
+       }
+
+ignore_func_on:
+       /* Apply the low power environment setup */
+       tci_sleep.mode = 0x5;
+       tci_sleep.duration = cpu_to_le16(0x640);
+       tci_sleep.host_duration = cpu_to_le16(0x640);
+       tci_sleep.host_wakeup_pin = 0;
+       tci_sleep.time_compensation = 0;
+
+       skb = __hci_cmd_sync(hdev, 0xfc7a, sizeof(tci_sleep), &tci_sleep,
+                            HCI_INIT_TIMEOUT);
+       if (IS_ERR(skb)) {
+               err = PTR_ERR(skb);
+               bt_dev_err(hdev, "Failed to apply low power setting (%d)", err);
+               return err;
+       }
+       kfree_skb(skb);
+
+       rettime = ktime_get();
+       delta = ktime_sub(rettime, calltime);
+       duration = (unsigned long long)ktime_to_ns(delta) >> 10;
+
+       bt_dev_info(hdev, "Device setup in %llu usecs", duration);
+
+       return 0;
+}
+
+static int btusb_mtk_shutdown(struct hci_dev *hdev)
+{
+       struct btmtk_hci_wmt_params wmt_params;
+       u8 param = 0;
+       int err;
+
+       /* Disable the device */
+       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;
+       }
+
+       return 0;
+}
+
+MODULE_FIRMWARE(FIRMWARE_MT7668);
+#endif
+
 #ifdef CONFIG_PM
 /* Configure an out-of-band gpio as wake-up pin, if specified in device tree */
 static int marvell_config_oob_wake(struct hci_dev *hdev)
@@ -3046,6 +3612,7 @@ static int btusb_probe(struct usb_interface *intf,
        init_usb_anchor(&data->bulk_anchor);
        init_usb_anchor(&data->isoc_anchor);
        init_usb_anchor(&data->diag_anchor);
+       init_usb_anchor(&data->ctrl_anchor);
        spin_lock_init(&data->rxlock);
 
        if (id->driver_info & BTUSB_INTEL_NEW) {
@@ -3159,6 +3726,15 @@ static int btusb_probe(struct usb_interface *intf,
        if (id->driver_info & BTUSB_MARVELL)
                hdev->set_bdaddr = btusb_set_bdaddr_marvell;
 
+#ifdef CONFIG_BT_HCIBTUSB_MTK
+       if (id->driver_info & BTUSB_MEDIATEK) {
+               hdev->setup = btusb_mtk_setup;
+               hdev->shutdown = btusb_mtk_shutdown;
+               hdev->manufacturer = 70;
+               set_bit(HCI_QUIRK_NON_PERSISTENT_SETUP, &hdev->quirks);
+       }
+#endif
+
        if (id->driver_info & BTUSB_SWAVE) {
                set_bit(HCI_QUIRK_FIXUP_INQUIRY_MODE, &hdev->quirks);
                set_bit(HCI_QUIRK_BROKEN_LOCAL_COMMANDS, &hdev->quirks);