diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 07e55cd..2788498 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -11,6 +11,10 @@ config BT_BCM
 	tristate
 	select FW_LOADER
 
+config BT_MTK
+	tristate
+	select FW_LOADER
+
 config BT_RTL
 	tristate
 	select FW_LOADER
@@ -52,6 +56,18 @@ 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
+	select BT_MTK
+	default y
+	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
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 4e4e44d..bc23724 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_WILINK)		+= btwilink.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
+obj-$(CONFIG_BT_MTK)		+= btmtk.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
 obj-$(CONFIG_BT_QCA)		+= btqca.o
 
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
new file mode 100644
index 0000000..9e39a0a
--- /dev/null
+++ b/drivers/bluetooth/btmtk.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 MediaTek Inc.
+
+/*
+ * Common operations for MediaTek Bluetooth devices
+ * with the UART, USB and SDIO transport
+ *
+ * Author: Sean Wang <sean.wang at mediatek.com>
+ *
+ */
+#include <asm/unaligned.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btmtk.h"
+
+#define VERSION "0.1"
+
+int
+btmtk_hci_wmt_sync(struct hci_dev *hdev, struct btmtk_hci_wmt_params *params)
+{
+	struct btmtk_hci_wmt_evt_funcc *evt_funcc;
+	u32 hlen, status = BTMTK_WMT_INVALID;
+	struct btmtk_wmt_hdr *hdr, *ehdr;
+	struct btmtk_hci_wmt_cmd wc;
+	struct sk_buff *skb;
+	int err = 0;
+
+	hlen = sizeof(*hdr) + params->dlen;
+	if (hlen > 255)
+		return -EINVAL;
+
+	hdr = (struct btmtk_wmt_hdr *)&wc;
+	hdr->dir = 1;
+	hdr->op = params->op;
+	hdr->dlen = cpu_to_le16(params->dlen + 1);
+	hdr->flag = params->flag;
+	memcpy(wc.data, params->data, params->dlen);
+
+	/* TODO: Add a fixup with __hci_raw_sync_ev that uses the hdev->raw_q
+	 * instead of the hack with __hci_cmd_sync_ev + atomic_inc on cmd_cnt.
+	 */
+	atomic_inc(&hdev->cmd_cnt);
+
+	skb =  __hci_cmd_sync_ev(hdev, 0xfc6f, hlen, &wc, HCI_VENDOR_PKT,
+				 HCI_INIT_TIMEOUT);
+	if (IS_ERR(skb)) {
+		err = PTR_ERR(skb);
+
+		bt_dev_err(hdev, "Failed to send wmt cmd (%d)\n", err);
+
+		print_hex_dump(KERN_ERR, "failed cmd: ",
+			       DUMP_PREFIX_ADDRESS, 16, 1, &wc,
+			       hlen > 16 ? 16 : hlen, true);
+		return err;
+	}
+
+	ehdr = (struct btmtk_wmt_hdr *)skb->data;
+	if (ehdr->op != hdr->op) {
+		bt_dev_err(hdev, "Wrong op received %d expected %d",
+			   ehdr->op, hdr->op);
+		err = -EIO;
+		goto err_free_skb;
+	}
+
+	switch (ehdr->op) {
+	case BTMTK_WMT_SEMAPHORE:
+		if (ehdr->flag == 2)
+			status = BTMTK_WMT_PATCH_UNDONE;
+		else
+			status = BTMTK_WMT_PATCH_DONE;
+		break;
+	case BTMTK_WMT_FUNC_CTRL:
+		evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)ehdr;
+		if (be16_to_cpu(evt_funcc->status) == 4)
+			status = BTMTK_WMT_ON_DONE;
+		else if (be16_to_cpu(evt_funcc->status) == 32)
+			status = BTMTK_WMT_ON_PROGRESS;
+		else
+			status = BTMTK_WMT_ON_UNDONE;
+		break;
+	};
+
+	if (params->status)
+		*params->status = status;
+
+err_free_skb:
+	kfree_skb(skb);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(btmtk_hci_wmt_sync);
+
+static int
+btmtk_setup_firmware(struct hci_dev *hdev, const char *fwname,
+		     int (*cmd_sync)(struct hci_dev *,
+				     struct btmtk_hci_wmt_params *))
+{
+	struct btmtk_hci_wmt_params wmt_params;
+	const struct firmware *fw;
+	const u8 *fw_ptr;
+	size_t fw_size;
+	int err, dlen;
+	u8 flag;
+
+	if (!cmd_sync)
+		return -EINVAL;
+
+	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)
+		return -EINVAL;
+
+	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 = cmd_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 = cmd_sync(hdev, &wmt_params);
+	if (err < 0) {
+		bt_dev_err(hdev, "Failed to send wmt rst (%d)", err);
+		return err;
+	}
+
+err_release_fw:
+	release_firmware(fw);
+
+	return err;
+}
+
+static int
+btmtk_func_query(struct btmtk_func_query *fq)
+{
+	struct btmtk_hci_wmt_params wmt_params;
+	int status, err;
+	u8 param = 0;
+
+	if (!fq || !fq->hdev || !fq->cmd_sync)
+		return -EINVAL;
+
+	/* 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 = fq->cmd_sync(fq->hdev, &wmt_params);
+	if (err < 0) {
+		bt_dev_err(fq->hdev, "Failed to query function status (%d)",
+			   err);
+		return err;
+	}
+
+	return status;
+}
+
+int btmtk_enable(struct hci_dev *hdev, const char *fwname,
+		 int (*cmd_sync)(struct hci_dev *hdev,
+				 struct btmtk_hci_wmt_params *))
+{
+	struct btmtk_hci_wmt_params wmt_params;
+	struct btmtk_func_query func_query;
+	int status, err;
+	u8 param;
+
+	if (!cmd_sync)
+		return -EINVAL;
+
+	/* 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 = cmd_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 = btmtk_setup_firmware(hdev, fwname, cmd_sync);
+	if (err < 0)
+		return err;
+
+ignore_setup_fw:
+	func_query.hdev = hdev;
+	func_query.cmd_sync = cmd_sync;
+	err = readx_poll_timeout(btmtk_func_query, &func_query, status,
+				 status < 0 || status != BTMTK_WMT_ON_PROGRESS,
+				 2000, 5000000);
+	/* -ETIMEDOUT happens */
+	if (err < 0)
+		return err;
+
+	/* The other errors happen internally inside btmtk_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 = cmd_sync(hdev, &wmt_params);
+	if (err < 0) {
+		bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err);
+		return err;
+	}
+
+ignore_func_on:
+	return 0;
+}
+EXPORT_SYMBOL_GPL(btmtk_enable);
+
+int btmtk_disable(struct hci_dev *hdev,
+		  int (*cmd_sync)(struct hci_dev *hdev,
+				  struct btmtk_hci_wmt_params *))
+{
+	struct btmtk_hci_wmt_params wmt_params;
+	u8 param = 0;
+	int err;
+
+	if (!cmd_sync)
+		return -EINVAL;
+
+	/* 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 = cmd_sync(hdev, &wmt_params);
+	if (err < 0) {
+		bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err);
+		return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(btmtk_disable);
+
+MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek Bluetooth device driver ver " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE_MT7668);
diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h
new file mode 100644
index 0000000..64fc395
--- /dev/null
+++ b/drivers/bluetooth/btmtk.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ *
+ * Common operations for MediaTek Bluetooth devices
+ * with the UART, USB and SDIO transport
+ *
+ * Author: Sean Wang <sean.wang@mediatek.com>
+ *
+ */
+
+#define FIRMWARE_MT7668		"mt7668pr2h.bin"
+
+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_funcc {
+	struct btmtk_wmt_hdr hdr;
+	__be16 status;
+} __packed;
+
+struct btmtk_hci_wmt_params {
+	u8 op;
+	u8 flag;
+	u16 dlen;
+	const void *data;
+	u32 *status;
+};
+
+struct btmtk_func_query {
+	struct hci_dev *hdev;
+	int (*cmd_sync)(struct hci_dev *hdev,
+			struct btmtk_hci_wmt_params *wmt_params);
+};
+
+#if IS_ENABLED(CONFIG_BT_MTK)
+
+int
+btmtk_hci_wmt_sync(struct hci_dev *hdev, struct btmtk_hci_wmt_params *params);
+
+int
+btmtk_enable(struct hci_dev *hdev, const char *fn,
+	     int (*cmd_sync)(struct hci_dev *,
+			     struct btmtk_hci_wmt_params *));
+
+int
+btmtk_disable(struct hci_dev *hdev,
+	      int (*cmd_sync)(struct hci_dev *,
+			      struct btmtk_hci_wmt_params *));
+#else
+
+static int
+btmtk_hci_wmt_sync(struct hci_dev *hdev, struct btmtk_hci_wmt_params *params)
+{
+	return -EOPNOTSUPP;
+}
+
+static int
+btmtk_enable(struct hci_dev *hdev, const char *fn,
+	     int (*cmd_sync)(struct hci_dev *,
+			     struct btmtk_hci_wmt_params *))
+{
+	return -EOPNOTSUPP;
+}
+
+static int
+btmtk_disable(struct hci_dev *hdev,
+	      int (*cmd_sync)(struct hci_dev *,
+			      struct btmtk_hci_wmt_params *))
+{
+	return -EOPNOTSUPP;
+}
+
+#endif
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 60bf04b..773238b 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -26,6 +26,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>
@@ -36,6 +37,7 @@
 
 #include "btintel.h"
 #include "btbcm.h"
+#include "btmtk.h"
 #include "btrtl.h"
 
 #define VERSION "0.8"
@@ -69,6 +71,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 */
@@ -355,6 +358,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 },
@@ -2347,6 +2354,164 @@ static int btusb_shutdown_intel(struct hci_dev *hdev)
 	return 0;
 }
 
+#ifdef CONFIG_BT_HCIBTUSB_MTK
+
+struct btusb_mtk_poll {
+	struct btusb_data *udata;
+	void *buf;
+	size_t len;
+	size_t actual_len;
+};
+
+struct btusb_mtk_wmt_poll {
+	struct btusb_data *udata;
+	struct work_struct work;
+};
+
+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_wmt_event_poll(struct btusb_mtk_poll *p)
+{
+	int pipe, actual_len;
+
+	pipe = usb_rcvctrlpipe(p->udata->udev, 0);
+
+	actual_len = usb_control_msg(p->udata->udev, pipe, 1,
+				     USB_TYPE_VENDOR | USB_DIR_IN, 0x30, 0,
+				     p->buf, p->len, USB_CTRL_SET_TIMEOUT);
+
+	p->actual_len = actual_len;
+
+	return actual_len;
+}
+
+static void btusb_mtk_wmt_event_polls(struct work_struct *work)
+{
+	struct btusb_mtk_wmt_poll *wmt_event_polling;
+	struct btusb_mtk_poll p;
+	int polled_dlen, err;
+	const int len = 64;
+	void *buf;
+	char *evt;
+
+	wmt_event_polling = container_of(work, typeof(*wmt_event_polling),
+					 work);
+	buf = kzalloc(len, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	p.udata = wmt_event_polling->udata;
+	p.buf = buf;
+	p.len = len;
+	p.actual_len = 0;
+
+	/* Polling WMT event via control endpoint until the event returns or
+	 * the timeout happens.
+	 */
+	err = readx_poll_timeout(btusb_mtk_wmt_event_poll, &p, polled_dlen,
+				 polled_dlen > 0, 200, 1000000);
+	if (err < 0)
+		goto err_free_buf;
+
+	evt = p.buf;
+
+	/* 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.
+	 */
+	if (*evt == 0xe4)
+		*evt = 0xff;
+
+	/* The WMT event is actually a HCI event so that the WMT event should go
+	 * to the code flow a HCI event should go to.
+	 */
+	btusb_recv_intr(p.udata, p.buf, p.actual_len);
+
+err_free_buf:
+	kfree(buf);
+}
+
+static int btusb_mtk_hci_wmt_sync(struct hci_dev *hdev,
+				  struct btmtk_hci_wmt_params *wmt_params)
+{
+	struct btusb_mtk_wmt_poll wmt_event_polling;
+	int err;
+
+	/* MediaTek WMT HCI vendor event is coming through the control endpoint,
+	 * not through the interrupt endpoint so that we have to schedule a
+	 * work to poll the event.
+	 */
+	INIT_WORK_ONSTACK(&wmt_event_polling.work, btusb_mtk_wmt_event_polls);
+	wmt_event_polling.udata = hci_get_drvdata(hdev);
+	schedule_work(&wmt_event_polling.work);
+
+	err = btmtk_hci_wmt_sync(hdev, wmt_params);
+
+	cancel_work_sync(&wmt_event_polling.work);
+
+	return err;
+}
+
+static int btusb_mtk_setup(struct hci_dev *hdev)
+{
+	struct btusb_data *data = hci_get_drvdata(hdev);
+	const char *fwname;
+	int err = 0;
+	u32 dev_id;
+
+	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;
+	}
+
+	return btmtk_enable(hdev, fwname, btusb_mtk_hci_wmt_sync);
+}
+
+static int btusb_mtk_shutdown(struct hci_dev *hdev)
+{
+	return btmtk_disable(hdev, btusb_mtk_hci_wmt_sync);
+}
+#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)
@@ -3031,6 +3196,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);
