Return-Path: From: To: , CC: , , , Sean Wang Subject: [PATCH v1 1/2] Bluetooth: mediatek: Add protocol support for MediaTek MT7668U USB devices Date: Sun, 12 Aug 2018 16:46:51 +0800 Message-ID: In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain List-ID: From: Sean Wang This adds the support of enabling MT7668U Bluetooth function running on the top of btusb driver. The patch also adds a newly created file mtkbt.c able to be reused independently from the transport type such as UART, USB and SDIO. Signed-off-by: Sean Wang --- drivers/bluetooth/Kconfig | 16 +++ drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btmtk.c | 308 +++++++++++++++++++++++++++++++++++++++++++++ drivers/bluetooth/btmtk.h | 99 +++++++++++++++ drivers/bluetooth/btusb.c | 174 +++++++++++++++++++++++++ 5 files changed, 598 insertions(+) create mode 100644 drivers/bluetooth/btmtk.c create mode 100644 drivers/bluetooth/btmtk.h 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 + * + */ +#include +#include +#include +#include + +#include +#include + +#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 = ¶m; + 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 = ¶m; + 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 = ¶m; + 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 "); +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 + * + */ + +#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 #include #include +#include #include #include #include @@ -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); -- 2.7.4