[Feature]add MT2731_MP2_MR2_SVN388 baseline version
Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/Android.mk b/src/kernel/modules/connectivity/bt_driver/mt66xx/Android.mk
new file mode 100644
index 0000000..8661892
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(MTK_BT_SUPPORT),true)
+ifneq ($(filter MTK_MT66%, $(MTK_BT_CHIP)),)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := bt_drv.ko
+LCOAL_PROPRIETARY_MODULE := true
+LOCAL_MODULE_OWNER := mtk
+LOCAL_INIT_RC := init.bt_drv.rc
+LOCAL_REQUIRED_MODULES := wmt_drv.ko
+
+include $(MTK_KERNEL_MODULE)
+
+endif
+endif
\ No newline at end of file
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/Makefile b/src/kernel/modules/connectivity/bt_driver/mt66xx/Makefile
new file mode 100644
index 0000000..60759b2
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/Makefile
@@ -0,0 +1,63 @@
+export KERNEL_SRC := /lib/modules/$(shell uname -r)/build
+
+$(warning $(CC))
+
+###############################################################################
+# stp_cdev_bt
+###############################################################################
+
+ifneq ($(findstring $(PLATFORM), MT8133_YOCTO MT8512_YOCTO mt6771),)
+ MODULE_NAME = wmt_cdev_bt
+else
+ MODULE_NAME = bt_drv
+endif
+
+$(MODULE_NAME)-objs := stp_chrdev_bt.o
+ifneq ($(CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH),)
+$(MODULE_NAME)-objs += fw_log_bt.o
+endif
+###############################################################################
+# Common
+###############################################################################
+EXTRA_SYMBOLS = ${TOPDIR}/tmp/work/${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}/mt66xx-wmt-drv/1.0-r0/mt66xx-wmt-drv-1.0/Module.symvers
+
+ccflags-y += -fno-pic
+ccflags-y += -D MTK_WCN_WMT_STP_EXP_SYMBOL_ABSTRACT
+ccflags-y += -D CREATE_NODE_DYNAMIC=1
+ccflags-y += -D CFG_MTK_ANDROID_WMT=1
+ccflags-y += \
+ -I$(CONNECTIVITY_SRC)/wmt_mt66xx/common_main/include \
+ -I$(CONNECTIVITY_SRC)/wmt_mt66xx/common_main/linux/include \
+ -I$(CONNECTIVITY_SRC)/../../linux/${KERNEL_VER}/drivers/misc/mediatek/include/mt-plat
+ifneq ($(CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH),)
+ccflags-y += -I$(CONNECTIVITY_SRC)/wmt_mt66xx/debug_utility
+endif
+
+WMT_SRC_FOLDER := $(TOP)/vendor/mediatek/kernel_modules/connectivity/common
+
+ccflags-y += \
+ -I$(WMT_SRC_FOLDER)/common_main/include \
+ -I$(WMT_SRC_FOLDER)/common_main/linux/include \
+ -I$(srctree)/drivers/misc/mediatek/include/mt-plat
+
+obj-m := $(MODULE_NAME).o
+
+ifeq ($(CONFIG_MTK_CONN_LTE_IDC_SUPPORT),y)
+ ccflags-y += -D WMT_IDC_SUPPORT=1
+else
+ ccflags-y += -D WMT_IDC_SUPPORT=0
+endif
+
+all:
+ make -C $(LINUX_SRC) M=$(PWD) modules
+
+bt_drv:
+ifneq ($(findstring $(PLATFORM), MT8133_YOCTO MT8512_YOCTO MT2712_YOCTO 2731_YOCTO mt6771),)
+ make -C $(LINUX_SRC) M=$(PWD) modules
+else
+ make -C $(LINUX_SRC) M=$(PWD) $(MODULE_NAME).ko
+endif
+
+clean:
+ make -C $(LINUX_SRC) M=$(PWD) clean
+
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/bt.h b/src/kernel/modules/connectivity/bt_driver/mt66xx/bt.h
new file mode 100644
index 0000000..f4a4d33
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/bt.h
@@ -0,0 +1,53 @@
+#ifndef _BT_EXP_H_
+#define _BT_EXP_H_
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/sched.h>
+#include <asm/current.h>
+#include <linux/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/printk.h>
+#include <linux/uio.h>
+#include <linux/mutex.h>
+
+#include "wmt_exp.h"
+#include "stp_exp.h"
+
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+#include "connsys_debug_utility.h"
+
+/* Flags to control BT FW log flow */
+#define OFF 0x00
+#define ON 0xff
+
+/* *****************************************************************************************
+ * BT Logger Tool will send 3 levels(Low, SQC and Debug)
+ * Driver will not check its range so we can provide capability of extention.
+ ******************************************************************************************/
+#define DEFAULT_LEVEL 0x02 /* 0x00:OFF, 0x01: LOW POWER, 0x02: SQC, 0x03: DEBUG */
+
+extern int fw_log_bt_init(void);
+extern void fw_log_bt_exit(void);
+extern void bt_state_notify(UINT32 on_off);
+extern ssize_t send_hci_frame(const PUINT8 buf, size_t count);
+
+#endif
+
+enum {
+ BTDRV_FOPS_STATE_INIT = 0,
+ BTDRV_FOPS_STATE_OPENING,
+ BTDRV_FOPS_STATE_OPENED,
+ BTDRV_FOPS_STATE_CLOSING,
+};
+
+#endif
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/fw_log_bt.c b/src/kernel/modules/connectivity/bt_driver/mt66xx/fw_log_bt.c
new file mode 100644
index 0000000..6943fbb
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/fw_log_bt.c
@@ -0,0 +1,377 @@
+/*
+* Copyright (C) 2016 MediaTek Inc.
+*
+* This program is free software: you can redistribute it and/or modify it under the terms of the
+* GNU General Public License version 2 as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+* See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with this program.
+* If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+#include "bt.h"
+
+MODULE_LICENSE("Dual BSD/GPL");
+
+/*******************************************************************************
+* M A C R O S
+********************************************************************************
+*/
+
+#define PFX "[BTFWLOG]"
+#define BT_LOG_DBG 3
+#define BT_LOG_INFO 2
+#define BT_LOG_WARN 1
+#define BT_LOG_ERR 0
+
+static UINT32 gDbgLevel = BT_LOG_INFO;
+
+#define BT_LOG_PR_DBG(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_DBG) pr_info(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_INFO(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_INFO) pr_info(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_WARN(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_WARN) pr_warn(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_ERR(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_ERR) pr_err(PFX "%s: " fmt, __func__, ##arg); } while (0)
+
+#define BT_LOG_NODE_NAME "fw_log_bt"
+
+#define BT_FW_LOG_IOC_MAGIC (0xfc)
+#define BT_FW_LOG_IOCTL_ON_OFF _IOW(BT_FW_LOG_IOC_MAGIC, 0, int)
+#define BT_FW_LOG_IOCTL_SET_LEVEL _IOW(BT_FW_LOG_IOC_MAGIC, 1, int)
+#define BT_FW_LOG_IOCTL_GET_LEVEL _IOW(BT_FW_LOG_IOC_MAGIC, 2, int)
+
+static unsigned char g_bt_on = OFF;
+static unsigned char g_log_on = OFF;
+static unsigned char g_log_level = DEFAULT_LEVEL;
+static unsigned char g_log_current = OFF;
+
+#define BT_LOG_BUFFER_SIZE 512
+
+static struct cdev log_cdev;
+#if CREATE_NODE_DYNAMIC
+static struct class *log_class;
+static struct device *log_dev;
+#endif
+static dev_t devno;
+
+wait_queue_head_t BT_log_wq;
+
+static struct semaphore ioctl_mtx;
+
+static int ascii_to_hex(unsigned char ascii, unsigned char *hex)
+{
+ int ret = 0;
+
+ if('0' <= ascii && ascii <= '9')
+ *hex = ascii - '0';
+ else if ('a' <= ascii && ascii <= 'f')
+ *hex = ascii - 'a' + 10;
+ else if ('A' <= ascii && ascii <= 'F')
+ *hex = ascii - 'A' + 10;
+ else
+ ret = -1;
+
+ return ret;
+}
+
+static int set_fw_log(unsigned char flag)
+{
+ ssize_t retval = 0;
+
+ /* Opcode 0xfc5d TCI_MTK_DEBUG_VERSION_INFO */
+ unsigned char HCI_CMD_FW_LOG_DEBUG[] = {0x01, 0x5d, 0xfc, 0x04, 0x02, 0x00, 0x01, 0xff}; // Via EMI
+
+ HCI_CMD_FW_LOG_DEBUG[7] = flag;
+ BT_LOG_PR_INFO("hci_cmd: %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x\n",
+ HCI_CMD_FW_LOG_DEBUG[0], HCI_CMD_FW_LOG_DEBUG[1],
+ HCI_CMD_FW_LOG_DEBUG[2], HCI_CMD_FW_LOG_DEBUG[3],
+ HCI_CMD_FW_LOG_DEBUG[4], HCI_CMD_FW_LOG_DEBUG[5],
+ HCI_CMD_FW_LOG_DEBUG[6], HCI_CMD_FW_LOG_DEBUG[7]);
+
+ retval = send_hci_frame(HCI_CMD_FW_LOG_DEBUG, sizeof(HCI_CMD_FW_LOG_DEBUG));
+
+ if (likely(retval == sizeof(HCI_CMD_FW_LOG_DEBUG)))
+ return 0;
+ else if (retval < 0)
+ return retval;
+ else {
+ BT_LOG_PR_ERR("Only partial sent %zu bytes, but hci cmd has %zu bytes", retval, sizeof(HCI_CMD_FW_LOG_DEBUG));
+ return -EFAULT;
+ }
+}
+
+void bt_state_notify(UINT32 on_off)
+{
+ BT_LOG_PR_INFO("g_bt_on %d, on_off %d\n", g_bt_on, on_off);
+
+ if (g_bt_on == on_off) {
+ // no change.
+ } else {
+ // changed.
+ if (on_off == OFF) { // should turn off.
+ g_bt_on = OFF;
+ BT_LOG_PR_INFO("BT func off, no need to send hci cmd\n");
+ } else {
+ g_bt_on = ON;
+ if(g_log_current)
+ set_fw_log(g_log_current);
+ }
+ }
+}
+
+long fw_log_bt_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long retval = 0;
+ unsigned char log_tmp = OFF;
+
+ down(&ioctl_mtx);
+ switch (cmd) {
+ case BT_FW_LOG_IOCTL_ON_OFF:
+ /* connsyslogger daemon dynamically enable/disable Picus log */
+ BT_LOG_PR_INFO("BT_FW_LOG_IOCTL_ON_OFF: arg(%lu), g_bt_on(0x%02x), g_log_on(0x%02x), g_log_level(0x%02x), g_log_current(0x%02x)\n",
+ arg, g_bt_on, g_log_on, g_log_level, g_log_current);
+ log_tmp = (arg == 0 ? OFF: ON);
+ if (log_tmp == g_log_on) // no change
+ break;
+ else { // changed
+ g_log_on = log_tmp;
+ g_log_current = g_log_on & g_log_level;
+ if (g_bt_on)
+ retval = set_fw_log(g_log_current);
+ }
+ break;
+ case BT_FW_LOG_IOCTL_SET_LEVEL:
+ /* connsyslogger daemon dynamically set Picus log level */
+ BT_LOG_PR_INFO("BT_FW_LOG_IOCTL_SET_LEVEL: arg(%lu), g_bt_on(0x%02x), g_log_on(0x%02x), g_log_level(0x%02x), g_log_current(0x%02x)\n",
+ arg, g_bt_on, g_log_on, g_log_level, g_log_current);
+ log_tmp = (unsigned char)arg;
+ if(log_tmp == g_log_level) // no change
+ break;
+ else {
+ g_log_level = log_tmp;
+ g_log_current = g_log_on & g_log_level;
+ if (g_bt_on & g_log_on) // driver on and log on
+ retval = set_fw_log(g_log_current);
+ }
+ break;
+ case BT_FW_LOG_IOCTL_GET_LEVEL:
+ retval = g_log_level;
+ BT_LOG_PR_INFO("BT_FW_LOG_IOCTL_GET_LEVEL: %ld\n", retval);
+ break;
+ default:
+ BT_LOG_PR_ERR("Unknown cmd: 0x%08x\n", cmd);
+ retval = -EOPNOTSUPP;
+ break;
+ }
+
+ up(&ioctl_mtx);
+ return retval;
+}
+
+long fw_log_bt_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return fw_log_bt_unlocked_ioctl(filp, cmd, arg);
+}
+
+static void fw_log_bt_event_cb(void)
+{
+ BT_LOG_PR_DBG("fw_log_bt_event_cb");
+ wake_up_interruptible(&BT_log_wq);
+}
+
+static unsigned int fw_log_bt_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ poll_wait(file, &BT_log_wq, wait);
+ if (connsys_log_get_buf_size(CONNLOG_TYPE_BT) > 0) {
+ mask = (POLLIN | POLLRDNORM);
+ }
+ return mask;
+}
+
+static ssize_t fw_log_bt_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ ssize_t retval = 0;
+ UINT8 tmp_buf[BT_LOG_BUFFER_SIZE] = {0};
+ UINT8 hci_cmd[BT_LOG_BUFFER_SIZE] = {0};
+ UINT8 tmp = 0, tmp_h = 0;
+ size_t i = 0, j = 0, k = 0;
+
+ if(count >= BT_LOG_BUFFER_SIZE) {
+ BT_LOG_PR_ERR("write count %zd exceeds max buffer size %d", count, BT_LOG_BUFFER_SIZE);
+ retval = -EINVAL;
+ goto OUT;
+ }
+
+ if (copy_from_user(tmp_buf, buf, count)) {
+ BT_LOG_PR_ERR("copy_from_user failed!\n");
+ retval = -EFAULT;
+ } else {
+ BT_LOG_PR_INFO("adb input: %s, len %zd\n", tmp_buf, strlen(tmp_buf));
+ if (0 == memcmp(tmp_buf, "raw-hex,", strlen("raw-hex,"))) {
+ // Skip prefix
+ for(i = strlen("raw-hex,"); i < strlen(tmp_buf); i++) {
+ if(tmp_buf[i] == ' ') // get space
+ continue;
+ else if(tmp_buf[i] == '\r' || tmp_buf[i] =='\n') // get 0x0a('\n') or 0x0d('\r')
+ break;
+ // Two input char should turn to one byte
+ if (ascii_to_hex(tmp_buf[i], &tmp) == 0) {
+ if (j%2 == 0)
+ tmp_h = tmp;
+ else {
+ hci_cmd[k] = tmp_h * 16 + tmp;
+ BT_LOG_PR_DBG("hci_cmd[%zd] = 0x%02x\n", k, hci_cmd[k]);
+ k++;
+ }
+ } else {
+ BT_LOG_PR_ERR("get unexpected char %c\n", tmp_buf[i]);
+ retval = -EINVAL;
+ goto OUT;
+ }
+ j++;
+ }
+ }
+ // ONLY send hci cmd when BT func on
+ if (!g_bt_on) {
+ retval = -EIO;
+ BT_LOG_PR_ERR("BT func off, skip to send hci cmd\n");
+ } else {
+ retval = send_hci_frame(hci_cmd, k);
+ if (retval == k) //success
+ retval = count;
+ else
+ retval = -EFAULT;
+ }
+ { //check log setting cmd and save the log level in case of restart fwlog
+ UINT8 log_level_cmd[8] = { 0x01, 0x5d, 0xfc, 0x04, 0x02, 0x00, 0x01, 0x00 };
+ if (k == 8) {
+ if (memcmp(hci_cmd, log_level_cmd, 7) == 0) { //cmpare the first 7 bytes
+ g_log_current = ON & hci_cmd[7];
+ BT_LOG_PR_INFO("BT fwlog is set to level: 0x%x\n", g_log_current);
+ }
+ }
+ }
+ }
+OUT:
+
+ return retval;
+}
+
+static ssize_t fw_log_bt_read(struct file *filp, char __user *buf, size_t len, loff_t *f_pos)
+{
+ size_t ret = 0;
+
+ ret = connsys_log_read_to_user(CONNLOG_TYPE_BT, buf, len);
+ BT_LOG_PR_DBG("BT F/W log from connsys len %zd\n", ret);
+ return ret;
+}
+
+static int fw_log_bt_open(struct inode *inode, struct file *file)
+{
+ BT_LOG_PR_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
+ return 0;
+}
+
+static int fw_log_bt_close(struct inode *inode, struct file *file)
+{
+ BT_LOG_PR_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
+ return 0;
+}
+
+struct file_operations log_fops = {
+ .open = fw_log_bt_open,
+ .release = fw_log_bt_close,
+ .read = fw_log_bt_read,
+ .write = fw_log_bt_write,
+ .unlocked_ioctl = fw_log_bt_unlocked_ioctl,
+ .compat_ioctl = fw_log_bt_compat_ioctl,
+ .poll = fw_log_bt_poll
+};
+
+int fw_log_bt_init(void)
+{
+ INT32 alloc_ret = 0;
+ INT32 cdev_err = 0;
+
+ connsys_log_init(CONNLOG_TYPE_BT);
+
+ init_waitqueue_head(&BT_log_wq);
+ connsys_log_register_event_cb(CONNLOG_TYPE_BT, fw_log_bt_event_cb);
+ sema_init(&ioctl_mtx, 1);
+
+ /* Allocate char device */
+ alloc_ret = alloc_chrdev_region(&devno, 0, 1, BT_LOG_NODE_NAME);
+ if (alloc_ret) {
+ BT_LOG_PR_ERR("Failed to register device numbers\n");
+ return alloc_ret;
+ }
+
+ cdev_init(&log_cdev, &log_fops);
+ log_cdev.owner = THIS_MODULE;
+
+ cdev_err = cdev_add(&log_cdev, devno, 1);
+ if (cdev_err)
+ goto error;
+
+#if CREATE_NODE_DYNAMIC /* mknod replace */
+ log_class = class_create(THIS_MODULE, BT_LOG_NODE_NAME);
+ if (IS_ERR(log_class))
+ goto error;
+
+ log_dev = device_create(log_class, NULL, devno, NULL, BT_LOG_NODE_NAME);
+ if (IS_ERR(log_dev))
+ goto error;
+#endif
+
+ BT_LOG_PR_INFO("%s driver(major %d, minor %d) installed\n", BT_LOG_NODE_NAME, MAJOR(devno), MINOR(devno));
+ return 0;
+
+error:
+
+#if CREATE_NODE_DYNAMIC
+ if (log_dev && !IS_ERR(log_dev)) {
+ device_destroy(log_class, devno);
+ log_dev = NULL;
+ }
+ if (log_class && !IS_ERR(log_class)) {
+ class_destroy(log_class);
+ log_class = NULL;
+ }
+#endif
+ if (cdev_err == 0)
+ cdev_del(&log_cdev);
+
+ if (alloc_ret == 0)
+ unregister_chrdev_region(devno, 1);
+
+ return -1;
+}
+
+void fw_log_bt_exit(void)
+{
+ connsys_log_deinit(CONNLOG_TYPE_BT);
+
+ cdev_del(&log_cdev);
+ unregister_chrdev_region(devno, 1);
+
+#if CREATE_NODE_DYNAMIC
+ if (log_dev && !IS_ERR(log_dev)) {
+ device_destroy(log_class, devno);
+ log_dev = NULL;
+ }
+ if (log_class && !IS_ERR(log_class)) {
+ class_destroy(log_class);
+ log_class = NULL;
+ }
+#endif
+ BT_LOG_PR_INFO("%s driver removed\n", BT_LOG_NODE_NAME);
+}
+#endif
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/init.bt_drv.rc b/src/kernel/modules/connectivity/bt_driver/mt66xx/init.bt_drv.rc
new file mode 100644
index 0000000..4c8f8e4
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/init.bt_drv.rc
@@ -0,0 +1,5 @@
+
+# load bt_drv
+on property:vendor.connsys.driver.ready=yes
+ insmod /vendor/lib/modules/bt_drv.ko
+
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt.c b/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt.c
new file mode 100644
index 0000000..d895a15
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt.c
@@ -0,0 +1,908 @@
+/*
+* Copyright (C) 2016 MediaTek Inc.
+*
+* This program is free software: you can redistribute it and/or modify it under the terms of the
+* GNU General Public License version 2 as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+* See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with this program.
+* If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "bt.h"
+#include <linux/pm_wakeup.h>
+#include <linux/version.h>
+
+MODULE_LICENSE("Dual BSD/GPL");
+
+/*******************************************************************************
+* M A C R O S
+********************************************************************************
+*/
+#define BT_DRIVER_NAME "mtk_stp_bt_chrdev"
+#define BT_DEV_MAJOR 192
+
+#define PFX "[MTK-BT] "
+#define BT_LOG_DBG 3
+#define BT_LOG_INFO 2
+#define BT_LOG_WARN 1
+#define BT_LOG_ERR 0
+
+static UINT32 gDbgLevel = BT_LOG_INFO;
+
+#define BT_LOG_PR_DBG(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_DBG) pr_info(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_INFO(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_INFO) pr_info(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_WARN(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_WARN) pr_warn(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_ERR(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_ERR) pr_err(PFX "%s: " fmt, __func__, ##arg); } while (0)
+#define BT_LOG_PR_INFO_RATELIMITED(fmt, arg...) \
+ do { if (gDbgLevel >= BT_LOG_ERR) pr_info_ratelimited(PFX "%s: " fmt, __func__, ##arg); } while (0)
+
+#define VERSION "2.0"
+
+#define COMBO_IOC_MAGIC 0xb0
+#define COMBO_IOCTL_FW_ASSERT _IOW(COMBO_IOC_MAGIC, 0, int)
+#define COMBO_IOCTL_BT_SET_PSM _IOW(COMBO_IOC_MAGIC, 1, bool)
+#define COMBO_IOCTL_BT_IC_HW_VER _IOR(COMBO_IOC_MAGIC, 2, void*)
+#define COMBO_IOCTL_BT_IC_FW_VER _IOR(COMBO_IOC_MAGIC, 3, void*)
+
+#define BT_BUFFER_SIZE 2048
+#define FTRACE_STR_LOG_SIZE 256
+
+#define BT_NVRAM_IN_DTBO 1
+#if BT_NVRAM_IN_DTBO
+#include <linux/of.h>
+#define COMBO_IOCTL_BT_FW_CFG_SET_OFFSET _IOW(COMBO_IOC_MAGIC, 5U, int)
+#define COMBO_IOCTL_BT_FW_CFG_SET_LENGTH _IOW(COMBO_IOC_MAGIC, 6U, int)
+#define COMBO_IOCTL_BT_FW_CFG_GET_VALUE _IOR(COMBO_IOC_MAGIC, 7U, void*)
+// the record structure define of bt nvram file
+struct ap_nvram_btradio {
+ unsigned char addr[6]; // BT address
+ unsigned char Voice[2]; // Voice setting for SCO connection
+ unsigned char Codec[4]; // PCM codec setting
+ unsigned char Radio[6]; // RF configuration
+ unsigned char Sleep[7]; // Sleep mode configuration
+ unsigned char BtFTR[2]; // Other feature setting
+ unsigned char TxPWOffset[3];// TX power channel offset compensation
+ unsigned char CoexAdjust[6];// BT/WIFI coex performance adjust
+ unsigned char Reserved1[2]; // Reserved
+ unsigned char Reserved2[2]; // Reserved
+ unsigned char Reserved3[4]; // Reserved
+ unsigned char Reserved4[4]; // Reserved
+ unsigned char Reserved5[8]; // Reserved
+ unsigned char Reserved6[8]; // Reserved
+};
+union BT_NVRAM_DATA_T {
+ struct ap_nvram_btradio fields;
+ unsigned char raw[sizeof(struct ap_nvram_btradio)];
+};
+static union BT_NVRAM_DATA_T bt_fw_cfg;
+static int r_offset;
+static int r_length;
+static int r_flag;
+#endif
+/*******************************************************************************
+* P U B L I C D A T A
+********************************************************************************
+*/
+
+/*******************************************************************************
+* P R I V A T E D A T A
+********************************************************************************
+*/
+static INT32 BT_devs = 1;
+static INT32 BT_major = BT_DEV_MAJOR;
+module_param(BT_major, uint, 0);
+static struct cdev BT_cdev;
+#if CREATE_NODE_DYNAMIC
+static struct class *stpbt_class;
+static struct device *stpbt_dev;
+#endif
+
+static UINT8 i_buf[BT_BUFFER_SIZE]; /* Input buffer for read */
+static UINT8 o_buf[BT_BUFFER_SIZE]; /* Output buffer for write */
+
+static DEFINE_MUTEX(bt_fops_state_mutex);
+static int bt_fops_state = BTDRV_FOPS_STATE_INIT;
+
+static struct semaphore wr_mtx, rd_mtx;
+static struct wakeup_source *bt_wakelock;
+/* Wait queue for poll and read */
+static wait_queue_head_t inq;
+static DECLARE_WAIT_QUEUE_HEAD(BT_wq);
+static INT32 flag;
+static INT32 bt_ftrace_flag;
+/*
+ * Reset flag for whole chip reset scenario, to indicate reset status:
+ * 0 - normal, no whole chip reset occurs
+ * 1 - reset start
+ * 2 - reset end, have not sent Hardware Error event yet
+ * 3 - reset end, already sent Hardware Error event
+ */
+static UINT32 rstflag;
+static UINT8 HCI_EVT_HW_ERROR[] = {0x04, 0x10, 0x01, 0x00};
+static loff_t rd_offset;
+
+/*******************************************************************************
+* F U N C T I O N S
+********************************************************************************
+*/
+
+static INT32 ftrace_print(const PINT8 str, ...)
+{
+#ifdef CONFIG_TRACING
+ va_list args;
+ INT8 temp_string[FTRACE_STR_LOG_SIZE];
+
+ if (bt_ftrace_flag) {
+ va_start(args, str);
+ vsnprintf(temp_string, FTRACE_STR_LOG_SIZE, str, args);
+ va_end(args);
+ trace_printk("%s\n", temp_string);
+ }
+#endif
+ return 0;
+}
+
+static void bt_set_fops_state(int new_state)
+{
+ mutex_lock(&bt_fops_state_mutex);
+ bt_fops_state = new_state;
+ mutex_unlock(&bt_fops_state_mutex);
+}
+
+static size_t bt_report_hw_error(char *buf, size_t count, loff_t *f_pos)
+{
+ size_t bytes_rest, bytes_read;
+
+ if (*f_pos == 0)
+ BT_LOG_PR_INFO("Send Hardware Error event to stack to restart Bluetooth\n");
+
+ bytes_rest = sizeof(HCI_EVT_HW_ERROR) - *f_pos;
+ bytes_read = count < bytes_rest ? count : bytes_rest;
+ memcpy(buf, HCI_EVT_HW_ERROR + *f_pos, bytes_read);
+ *f_pos += bytes_read;
+
+ return bytes_read;
+}
+
+/*******************************************************************
+* WHOLE CHIP RESET message handler
+********************************************************************
+*/
+static VOID bt_cdev_rst_cb(ENUM_WMTDRV_TYPE_T src,
+ ENUM_WMTDRV_TYPE_T dst, ENUM_WMTMSG_TYPE_T type, PVOID buf, UINT32 sz)
+{
+ ENUM_WMTRSTMSG_TYPE_T rst_msg;
+
+ if (sz > sizeof(ENUM_WMTRSTMSG_TYPE_T)) {
+ BT_LOG_PR_WARN("Invalid message format!\n");
+ return;
+ }
+
+ memcpy((PINT8)&rst_msg, (PINT8)buf, sz);
+ BT_LOG_PR_DBG("src = %d, dst = %d, type = %d, buf = 0x%x sz = %d, max = %d\n",
+ src, dst, type, rst_msg, sz, WMTRSTMSG_RESET_MAX);
+ if ((src == WMTDRV_TYPE_WMT) && (dst == WMTDRV_TYPE_BT) && (type == WMTMSG_TYPE_RESET)) {
+ switch (rst_msg) {
+ case WMTRSTMSG_RESET_START:
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+ bt_state_notify(OFF);
+#endif
+ BT_LOG_PR_INFO("Whole chip reset start!\n");
+ rstflag = 1;
+ break;
+
+ case WMTRSTMSG_RESET_END:
+ case WMTRSTMSG_RESET_END_FAIL:
+ if (rst_msg == WMTRSTMSG_RESET_END)
+ BT_LOG_PR_INFO("Whole chip reset end!\n");
+ else
+ BT_LOG_PR_INFO("Whole chip reset fail!\n");
+ rd_offset = 0;
+ rstflag = 2;
+ flag = 1;
+ wake_up_interruptible(&inq);
+ wake_up(&BT_wq);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static VOID BT_event_cb(VOID)
+{
+ BT_LOG_PR_DBG("BT_event_cb\n");
+ ftrace_print("%s get called", __func__);
+
+ /*
+ * Hold wakelock for 100ms to avoid system enter suspend in such case:
+ * FW has sent data to host, STP driver received the data and put it
+ * into BT rx queue, then send sleep command and release wakelock as
+ * quick sleep mechanism for low power, BT driver will wake up stack
+ * hci thread stuck in poll or read.
+ * But before hci thread comes to read data, system enter suspend,
+ * hci command timeout timer keeps counting during suspend period till
+ * expires, then the RTC interrupt wakes up system, command timeout
+ * handler is executed and meanwhile the event is received.
+ * This will false trigger FW assert and should never happen.
+ */
+ __pm_wakeup_event(bt_wakelock, 100);
+
+ /*
+ * Finally, wake up any reader blocked in poll or read
+ */
+ flag = 1;
+ wake_up_interruptible(&inq);
+ wake_up(&BT_wq);
+ ftrace_print("%s wake_up triggered", __func__);
+}
+
+unsigned int BT_poll(struct file *filp, poll_table *wait)
+{
+ UINT32 mask = 0;
+
+ if ((mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0) ||
+ (rstflag == 1) || (rstflag == 3)) {
+ /*
+ * BT rx queue is empty, or whole chip reset start, or already sent Hardware Error event
+ * for whole chip reset end, add to wait queue.
+ */
+ poll_wait(filp, &inq, wait);
+ /*
+ * Check if condition changes before poll_wait return, in case of
+ * wake_up_interruptible is called before add_wait_queue, otherwise,
+ * do_poll will get into sleep and never be waken up until timeout.
+ */
+ if (!((mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0) ||
+ (rstflag == 1) || (rstflag == 3)))
+ mask |= POLLIN | POLLRDNORM; /* Readable */
+ } else {
+ /* BT rx queue has valid data, or whole chip reset end, have not sent Hardware Error event yet */
+ mask |= POLLIN | POLLRDNORM; /* Readable */
+ }
+
+ /* Do we need condition here? */
+ mask |= POLLOUT | POLLWRNORM; /* Writable */
+ ftrace_print("%s: return mask = 0x%04x", __func__, mask);
+
+ return mask;
+}
+
+static ssize_t __bt_write(const PUINT8 buffer, size_t count)
+{
+ INT32 retval = 0;
+
+ retval = mtk_wcn_stp_send_data(buffer, count, BT_TASK_INDX);
+
+ if (retval < 0)
+ BT_LOG_PR_ERR("mtk_wcn_stp_send_data fail, retval %d\n", retval);
+ else if (retval == 0) {
+ /* Device cannot process data in time, STP queue is full and no space is available for write,
+ * native program should not call writev with no delay.
+ */
+ BT_LOG_PR_INFO_RATELIMITED("write count %zd, sent bytes %d, no space is available!\n", count, retval);
+ retval = -EAGAIN;
+ } else
+ BT_LOG_PR_DBG("write count %zd, sent bytes %d\n", count, retval);
+
+ return retval;
+}
+
+ssize_t send_hci_frame(const PUINT8 buff, size_t count)
+{
+ ssize_t retval = 0;
+ int retry = 0;
+
+ down(&wr_mtx);
+
+ do {
+ if (retry > 0) {
+ msleep(30);
+ BT_LOG_PR_ERR("Send hci cmd failed, retry %d time(s)\n", retry);
+ }
+ retval = __bt_write(buff, count);
+ retry++;
+ } while (retval == -EAGAIN && retry < 3);
+
+ up(&wr_mtx);
+
+ return retval;
+}
+
+ssize_t BT_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ INT32 retval = 0;
+ size_t count = iov_iter_count(from);
+
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&wr_mtx);
+
+ BT_LOG_PR_DBG("count %zd", count);
+
+ if (rstflag) {
+ BT_LOG_PR_ERR("whole chip reset occurs! rstflag=%d\n", rstflag);
+ retval = -EIO;
+ goto OUT;
+ }
+
+ if (count > 0) {
+ if (count > BT_BUFFER_SIZE) {
+ BT_LOG_PR_ERR("write count %zd exceeds max buffer size %d", count, BT_BUFFER_SIZE);
+ retval = -EINVAL;
+ goto OUT;
+ }
+
+ if (copy_from_iter(o_buf, count, from) != count) {
+ retval = -EFAULT;
+ goto OUT;
+ }
+
+ retval = __bt_write(o_buf, count);
+ }
+
+OUT:
+ up(&wr_mtx);
+ return retval;
+}
+
+ssize_t BT_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ INT32 retval = 0;
+
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&wr_mtx);
+
+ BT_LOG_PR_DBG("count %zd pos %lld\n", count, *f_pos);
+
+ if (rstflag) {
+ BT_LOG_PR_ERR("whole chip reset occurs! rstflag=%d\n", rstflag);
+ retval = -EIO;
+ goto OUT;
+ }
+
+ if (count > 0) {
+ if (count > BT_BUFFER_SIZE) {
+ BT_LOG_PR_ERR("write count %zd exceeds max buffer size %d", count, BT_BUFFER_SIZE);
+ retval = -EINVAL;
+ goto OUT;
+ }
+
+ if (copy_from_user(o_buf, buf, count)) {
+ retval = -EFAULT;
+ goto OUT;
+ }
+
+ retval = __bt_write(o_buf, count);
+ }
+
+OUT:
+ up(&wr_mtx);
+ return retval;
+}
+
+ssize_t BT_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ INT32 retval = 0;
+
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&rd_mtx);
+
+ BT_LOG_PR_DBG("count %zd pos %lld\n", count, *f_pos);
+
+ if (rstflag) {
+ while (rstflag != 2) {
+ /*
+ * If nonblocking mode, return directly.
+ * O_NONBLOCK is specified during open()
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ BT_LOG_PR_ERR("Non-blocking read, whole chip reset occurs! rstflag=%d\n", rstflag);
+ retval = -EIO;
+ goto OUT;
+ }
+
+ wait_event(BT_wq, flag != 0);
+ flag = 0;
+ }
+ /*
+ * Reset end, send Hardware Error event to stack only once.
+ * To avoid high frequency read from stack before process is killed, set rstflag to 3
+ * to block poll and read after Hardware Error event is sent.
+ */
+ retval = bt_report_hw_error(i_buf, count, &rd_offset);
+ if (rd_offset == sizeof(HCI_EVT_HW_ERROR)) {
+ rd_offset = 0;
+ rstflag = 3;
+ }
+
+ if (copy_to_user(buf, i_buf, retval)) {
+ retval = -EFAULT;
+ if (rstflag == 3)
+ rstflag = 2;
+ }
+
+ goto OUT;
+ }
+
+ if (count > BT_BUFFER_SIZE) {
+ count = BT_BUFFER_SIZE;
+ BT_LOG_PR_WARN("Shorten read count from %zd to %d\n", count, BT_BUFFER_SIZE);
+ }
+
+ do {
+ retval = mtk_wcn_stp_receive_data(i_buf, count, BT_TASK_INDX);
+ if (retval < 0) {
+ BT_LOG_PR_ERR("mtk_wcn_stp_receive_data fail, retval %d\n", retval);
+ goto OUT;
+ } else if (retval == 0) { /* Got nothing, wait for STP's signal */
+ /*
+ * If nonblocking mode, return directly.
+ * O_NONBLOCK is specified during open()
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ BT_LOG_PR_ERR("Non-blocking read, no data is available!\n");
+ retval = -EAGAIN;
+ goto OUT;
+ }
+
+ wait_event(BT_wq, flag != 0);
+ flag = 0;
+ } else { /* Got something from STP driver */
+ BT_LOG_PR_DBG("Read bytes %d\n", retval);
+ break;
+ }
+ } while (!mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0);
+
+ if (retval == 0) {
+ if (rstflag != 2) { /* Should never happen */
+ WARN(1, "Blocking read is waken up with no data but rstflag=%d\n", rstflag);
+ retval = -EIO;
+ goto OUT;
+ } else { /* Reset end, send Hardware Error event only once */
+ retval = bt_report_hw_error(i_buf, count, &rd_offset);
+ if (rd_offset == sizeof(HCI_EVT_HW_ERROR)) {
+ rd_offset = 0;
+ rstflag = 3;
+ }
+ }
+ }
+
+ if (copy_to_user(buf, i_buf, retval)) {
+ retval = -EFAULT;
+ if (rstflag == 3)
+ rstflag = 2;
+ }
+
+OUT:
+ up(&rd_mtx);
+ return retval;
+}
+
+#if BT_NVRAM_IN_DTBO
+static void BT_get_fw_cfg(void)
+{
+ struct device_node *node;
+ memset(&bt_fw_cfg, 0, sizeof(union BT_NVRAM_DATA_T));
+ r_offset = 0;
+ r_length = 0;
+ r_flag = 0;
+ node = of_find_compatible_node(NULL, NULL,
+ "mediatek,connectivity-combo");
+ if (node) {
+ const UINT8 *prop;
+ prop = of_get_property(node, "btAddr", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.addr, prop,
+ 6 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[BDAddr %02x-%02x-%02x-%02x-%02x-%02x]\n",
+ bt_fw_cfg.fields.addr[0],
+ bt_fw_cfg.fields.addr[1],
+ bt_fw_cfg.fields.addr[2],
+ bt_fw_cfg.fields.addr[3],
+ bt_fw_cfg.fields.addr[4],
+ bt_fw_cfg.fields.addr[5]);
+ }
+ prop = of_get_property(node, "btVoice", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.Voice, prop,
+ 2 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[Voice %02x %02x]\n",
+ bt_fw_cfg.fields.Voice[0],
+ bt_fw_cfg.fields.Voice[1]);
+ }
+ prop = of_get_property(node, "btCodec", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.Codec, prop,
+ 4 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[Codec %02x %02x %02x %02x]\n",
+ bt_fw_cfg.fields.Codec[0],
+ bt_fw_cfg.fields.Codec[1],
+ bt_fw_cfg.fields.Codec[2],
+ bt_fw_cfg.fields.Codec[3]);
+ }
+ prop = of_get_property(node, "btRadio", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.Radio, prop,
+ 6 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[Radio %02x %02x %02x %02x %02x %02x]\n",
+ bt_fw_cfg.fields.Radio[0],
+ bt_fw_cfg.fields.Radio[1],
+ bt_fw_cfg.fields.Radio[2],
+ bt_fw_cfg.fields.Radio[3],
+ bt_fw_cfg.fields.Radio[4],
+ bt_fw_cfg.fields.Radio[5]);
+ }
+ prop = of_get_property(node, "btSleep", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.Sleep, prop,
+ 7 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[Sleep %02x %02x %02x %02x %02x %02x %02x]\n",
+ bt_fw_cfg.fields.Sleep[0],
+ bt_fw_cfg.fields.Sleep[1],
+ bt_fw_cfg.fields.Sleep[2],
+ bt_fw_cfg.fields.Sleep[3],
+ bt_fw_cfg.fields.Sleep[4],
+ bt_fw_cfg.fields.Sleep[5],
+ bt_fw_cfg.fields.Sleep[6]);
+ }
+ prop = of_get_property(node, "btFtr", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.BtFTR, prop,
+ 2 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[BtFTR %02x %02x]\n",
+ bt_fw_cfg.fields.BtFTR[0],
+ bt_fw_cfg.fields.BtFTR[1]);
+ }
+ prop = of_get_property(node, "btTxPwOffset", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.TxPWOffset, prop,
+ 3 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[TxPWOffset %02x %02x %02x]\n",
+ bt_fw_cfg.fields.TxPWOffset[0],
+ bt_fw_cfg.fields.TxPWOffset[1],
+ bt_fw_cfg.fields.TxPWOffset[2]);
+ }
+ prop = of_get_property(node, "btCoexAdjust", NULL);
+ if (prop != NULL) {
+ memcpy(bt_fw_cfg.fields.CoexAdjust, prop,
+ 6 * sizeof(unsigned char));
+ BT_LOG_PR_DBG("[CoexAdj %02x %02x %02x %02x %02x %02x]\n",
+ bt_fw_cfg.fields.CoexAdjust[0],
+ bt_fw_cfg.fields.CoexAdjust[1],
+ bt_fw_cfg.fields.CoexAdjust[2],
+ bt_fw_cfg.fields.CoexAdjust[3],
+ bt_fw_cfg.fields.CoexAdjust[4],
+ bt_fw_cfg.fields.CoexAdjust[5]);
+ }
+ } else
+ BT_LOG_PR_ERR("find combo node failed!\n");
+}
+#endif
+/* int BT_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) */
+long BT_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ INT32 retval = 0;
+ UINT32 reason;
+ UINT32 ver = 0;
+
+ BT_LOG_PR_DBG("cmd: 0x%08x\n", cmd);
+
+ switch (cmd) {
+ case COMBO_IOCTL_FW_ASSERT:
+ /* Trigger FW assert for debug */
+ reason = (UINT32)arg & 0xFFFF;
+ BT_LOG_PR_INFO("Host trigger FW assert......, reason:%d\n", reason);
+ if (reason == 31) /* HCI command timeout */
+ BT_LOG_PR_INFO("HCI command timeout OpCode 0x%04x\n", ((UINT32)arg >> 16) & 0xFFFF);
+
+ if (mtk_wcn_wmt_assert(WMTDRV_TYPE_BT, reason) == MTK_WCN_BOOL_TRUE) {
+ BT_LOG_PR_INFO("Host trigger FW assert succeed\n");
+ retval = 0;
+ } else {
+ BT_LOG_PR_ERR("Host trigger FW assert failed\n");
+ retval = -EBUSY;
+ }
+ break;
+ case COMBO_IOCTL_BT_SET_PSM:
+ /* BT stack may need to dynamically enable/disable Power Saving Mode
+ * in some scenarios for performance, e.g. A2DP chopping.
+ */
+ BT_LOG_PR_INFO("BT stack change PSM setting: %lu\n", arg);
+ retval = mtk_wcn_wmt_psm_ctrl((MTK_WCN_BOOL)arg);
+ break;
+ case COMBO_IOCTL_BT_IC_HW_VER:
+ ver = mtk_wcn_wmt_ic_info_get(WMTCHIN_HWVER);
+ BT_LOG_PR_INFO("HW ver: 0x%x\n", ver);
+ if (copy_to_user((UINT32 __user *)arg, &ver, sizeof(ver)))
+ retval = -EFAULT;
+ break;
+ case COMBO_IOCTL_BT_IC_FW_VER:
+ ver = mtk_wcn_wmt_ic_info_get(WMTCHIN_FWVER);
+ BT_LOG_PR_INFO("FW ver: 0x%x\n", ver);
+ if (copy_to_user((UINT32 __user *)arg, &ver, sizeof(ver)))
+ retval = -EFAULT;
+ break;
+#if BT_NVRAM_IN_DTBO
+ case COMBO_IOCTL_BT_FW_CFG_SET_OFFSET:
+ r_offset = (int)arg;
+ if (r_offset > sizeof(struct ap_nvram_btradio)) {
+ BT_LOG_PR_ERR("Error OFFSET: %d\n", r_offset);
+ retval = -EOPNOTSUPP;
+ } else {
+ r_flag |= 0x1;
+ }
+ break;
+ case COMBO_IOCTL_BT_FW_CFG_SET_LENGTH:
+ r_length = (int)arg;
+ if (r_offset + r_length > sizeof(struct ap_nvram_btradio)) {
+ BT_LOG_PR_ERR("Error length: %d %d\n",
+ r_offset, r_length);
+ retval = -EOPNOTSUPP;
+ } else {
+ r_flag |= 0x2;
+ }
+ break;
+ case COMBO_IOCTL_BT_FW_CFG_GET_VALUE:
+ if (r_flag != 3) {
+ BT_LOG_PR_ERR("Error flag: %d\n", r_flag);
+ retval = -EOPNOTSUPP;
+ } else {
+ if (copy_to_user((char __user *)arg,
+ &bt_fw_cfg.raw[r_offset],
+ r_length) != 0UL)
+ retval = -EFAULT;
+ }
+ r_flag = 0;
+ break;
+#endif
+ default:
+ BT_LOG_PR_ERR("Unknown cmd: 0x%08x\n", cmd);
+ retval = -EOPNOTSUPP;
+ break;
+ }
+
+ return retval;
+}
+
+long BT_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ return BT_unlocked_ioctl(filp, cmd, arg);
+}
+
+static int BT_open(struct inode *inode, struct file *file)
+{
+ BT_LOG_PR_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
+
+ mutex_lock(&bt_fops_state_mutex);
+ if ((bt_fops_state == BTDRV_FOPS_STATE_OPENING)
+ || (bt_fops_state == BTDRV_FOPS_STATE_OPENED)){
+ BT_LOG_PR_ERR("BT is in using, fops_state = %d\n", bt_fops_state);
+ mutex_unlock(&bt_fops_state_mutex);
+ return -EBUSY;
+ } else if (bt_fops_state == BTDRV_FOPS_STATE_CLOSING) {
+ BT_LOG_PR_ERR("BT is closing, fops_state = %d\n", bt_fops_state);
+ mutex_unlock(&bt_fops_state_mutex);
+ return -EAGAIN;
+ }
+ bt_fops_state = BTDRV_FOPS_STATE_OPENING;
+ mutex_unlock(&bt_fops_state_mutex);
+
+ /* Turn on BT */
+ if (mtk_wcn_wmt_func_on(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
+ BT_LOG_PR_ERR("WMT turn on BT fail!\n");
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ return -EIO;
+ }
+
+ BT_LOG_PR_INFO("WMT turn on BT OK!\n");
+
+ if (mtk_wcn_stp_is_ready() == MTK_WCN_BOOL_FALSE) {
+
+ BT_LOG_PR_ERR("STP is not ready!\n");
+ mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT);
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ return -EIO;
+ }
+
+ mtk_wcn_stp_set_bluez(0);
+
+ BT_LOG_PR_INFO("Now it's in MTK Bluetooth Mode\n");
+ BT_LOG_PR_INFO("STP is ready!\n");
+
+ BT_LOG_PR_DBG("Register BT event callback!\n");
+ mtk_wcn_stp_register_event_cb(BT_TASK_INDX, BT_event_cb);
+
+ BT_LOG_PR_DBG("Register BT reset callback!\n");
+ mtk_wcn_wmt_msgcb_reg(WMTDRV_TYPE_BT, bt_cdev_rst_cb);
+
+ rstflag = 0;
+ bt_ftrace_flag = 1;
+
+ sema_init(&wr_mtx, 1);
+ sema_init(&rd_mtx, 1);
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+ bt_state_notify(ON);
+#endif
+ bt_set_fops_state(BTDRV_FOPS_STATE_OPENED);
+
+ return 0;
+}
+
+static int BT_close(struct inode *inode, struct file *file)
+{
+ BT_LOG_PR_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
+
+ mutex_lock(&bt_fops_state_mutex);
+ if (bt_fops_state != BTDRV_FOPS_STATE_OPENED) {
+ BT_LOG_PR_ERR("BT is not opened, fops_state = %d\n", bt_fops_state);
+ mutex_unlock(&bt_fops_state_mutex);
+ return 0;
+ }
+ bt_fops_state = BTDRV_FOPS_STATE_CLOSING;
+ mutex_unlock(&bt_fops_state_mutex);
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+ bt_state_notify(OFF);
+#endif
+
+ rstflag = 0;
+ bt_ftrace_flag = 0;
+ mtk_wcn_wmt_msgcb_unreg(WMTDRV_TYPE_BT);
+ mtk_wcn_stp_register_event_cb(BT_TASK_INDX, NULL);
+
+ if (mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
+ BT_LOG_PR_ERR("WMT turn off BT fail!\n");
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ return -EIO; /* Mostly, native program will not check this return value. */
+ }
+
+ BT_LOG_PR_INFO("WMT turn off BT OK!\n");
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ return 0;
+}
+
+const struct file_operations BT_fops = {
+ .open = BT_open,
+ .release = BT_close,
+ .read = BT_read,
+ .write = BT_write,
+ .write_iter = BT_write_iter,
+ /* .ioctl = BT_ioctl, */
+ .unlocked_ioctl = BT_unlocked_ioctl,
+ .compat_ioctl = BT_compat_ioctl,
+ .poll = BT_poll
+};
+
+static int BT_init(void)
+{
+ dev_t dev;
+ INT32 alloc_ret = 0;
+ INT32 cdev_err = 0;
+ dev = MKDEV(BT_major, 0);
+
+ /* Initialize wait queue */
+ init_waitqueue_head(&(inq));
+ /* Initialize wake lock */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 80)
+ bt_wakelock = wakeup_source_register(NULL, "bt_drv");
+#else
+ bt_wakelock = wakeup_source_register("bt_drv");
+#endif
+ if(!bt_wakelock) {
+ BT_LOG_PR_ERR("%s: init bt_wakelock failed!\n", __func__);
+ }
+
+ /* Allocate char device */
+ alloc_ret = register_chrdev_region(dev, BT_devs, BT_DRIVER_NAME);
+ if (alloc_ret) {
+ BT_LOG_PR_ERR("Failed to register device numbers\n");
+ return alloc_ret;
+ }
+
+ cdev_init(&BT_cdev, &BT_fops);
+ BT_cdev.owner = THIS_MODULE;
+
+ cdev_err = cdev_add(&BT_cdev, dev, BT_devs);
+ if (cdev_err)
+ goto error;
+
+#if CREATE_NODE_DYNAMIC /* mknod replace */
+ stpbt_class = class_create(THIS_MODULE, "stpbt");
+ if (IS_ERR(stpbt_class))
+ goto error;
+ stpbt_dev = device_create(stpbt_class, NULL, dev, NULL, "stpbt");
+ if (IS_ERR(stpbt_dev))
+ goto error;
+#endif
+
+ BT_LOG_PR_INFO("%s driver(major %d) installed\n", BT_DRIVER_NAME, BT_major);
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+ fw_log_bt_init();
+#endif
+#if BT_NVRAM_IN_DTBO
+ BT_get_fw_cfg();
+#endif
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ return 0;
+
+error:
+#if CREATE_NODE_DYNAMIC
+ if (stpbt_dev && !IS_ERR(stpbt_dev)) {
+ device_destroy(stpbt_class, dev);
+ stpbt_dev = NULL;
+ }
+ if (stpbt_class && !IS_ERR(stpbt_class)) {
+ class_destroy(stpbt_class);
+ stpbt_class = NULL;
+ }
+#endif
+ if (cdev_err == 0)
+ cdev_del(&BT_cdev);
+
+ if (alloc_ret == 0)
+ unregister_chrdev_region(dev, BT_devs);
+
+ return -1;
+}
+
+static void BT_exit(void)
+{
+ dev_t dev;
+
+#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
+ fw_log_bt_exit();
+#endif
+
+ dev = MKDEV(BT_major, 0);
+ /* Destroy wake lock*/
+ wakeup_source_unregister(bt_wakelock);
+
+#if CREATE_NODE_DYNAMIC
+ if (stpbt_dev && !IS_ERR(stpbt_dev)) {
+ device_destroy(stpbt_class, dev);
+ stpbt_dev = NULL;
+ }
+ if (stpbt_class && !IS_ERR(stpbt_class)) {
+ class_destroy(stpbt_class);
+ stpbt_class = NULL;
+ }
+#endif
+
+ cdev_del(&BT_cdev);
+ unregister_chrdev_region(dev, BT_devs);
+
+ bt_set_fops_state(BTDRV_FOPS_STATE_INIT);
+ BT_LOG_PR_INFO("%s driver removed\n", BT_DRIVER_NAME);
+}
+
+#ifdef MTK_WCN_REMOVE_KERNEL_MODULE
+
+int mtk_wcn_stpbt_drv_init(void)
+{
+ return BT_init();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_init);
+
+void mtk_wcn_stpbt_drv_exit(void)
+{
+ return BT_exit();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_exit);
+
+#else
+
+module_init(BT_init);
+module_exit(BT_exit);
+
+#endif
diff --git a/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt_bluez.c b/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt_bluez.c
new file mode 100644
index 0000000..134604a
--- /dev/null
+++ b/src/kernel/modules/connectivity/bt_driver/mt66xx/stp_chrdev_bt_bluez.c
@@ -0,0 +1,1014 @@
+/*
+* Copyright (C) 2016 MediaTek Inc.
+*
+* This program is free software: you can redistribute it and/or modify it under the terms of the
+* GNU General Public License version 2 as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+* See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with this program.
+* If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/sched.h>
+#include <asm/current.h>
+#include <linux/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/printk.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "wmt_exp.h"
+#include "stp_exp.h"
+
+MODULE_LICENSE("Dual BSD/GPL");
+
+#define BT_DRIVER_NAME "MTK BT"
+
+#define BTMTK_LOG_LEVEL_ERROR 1
+#define BTMTK_LOG_LEVEL_WARNING 2
+#define BTMTK_LOG_LEVEL_INFO 3
+#define BTMTK_LOG_LEVEL_DEBUG 4
+
+unsigned char btmtk_log_lvl = BTMTK_LOG_LEVEL_INFO;
+
+#define BTMTK_ERR(fmt, ...) \
+ do {if (btmtk_log_lvl >= BTMTK_LOG_LEVEL_ERROR) pr_warn("btmtk_err: "fmt"\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_WARN(fmt, ...) \
+ do {if (btmtk_log_lvl >= BTMTK_LOG_LEVEL_WARNING) pr_warn("btmtk_warn: "fmt"\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_INFO(fmt, ...) \
+ do {if (btmtk_log_lvl >= BTMTK_LOG_LEVEL_INFO) pr_warn("btmtk_info: "fmt"\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_DBG(fmt, ...) \
+ do {if (btmtk_log_lvl >= BTMTK_LOG_LEVEL_DEBUG) pr_warn("btmtk_debug: "fmt"\n", ##__VA_ARGS__); } while (0)
+
+struct btmtk_thread {
+ struct task_struct *task;
+ wait_queue_head_t wait_q;
+ void *priv;
+};
+
+struct btmtk_device {
+ void *card;
+ struct hci_dev *hcidev;
+
+ unsigned char dev_type;
+
+ unsigned char tx_dnld_rdy;
+
+ unsigned char psmode;
+ unsigned char pscmd;
+ unsigned char hsmode;
+ unsigned char hscmd;
+
+ /* Low byte is gap, high byte is GPIO */
+ unsigned short gpio_gap;
+
+ unsigned char hscfgcmd;
+ unsigned char sendcmdflag;
+};
+
+struct btmtk_adapter {
+ void *hw_regs_buf;
+ unsigned char *hw_regs;
+ unsigned int int_count;
+ struct sk_buff_head tx_queue;
+ struct sk_buff_head fops_queue;
+ struct sk_buff_head fwlog_fops_queue;
+ struct sk_buff_head fwlog_tx_queue;
+ unsigned char fops_mode;
+ unsigned char psmode;
+ unsigned char ps_state;
+ unsigned char hs_state;
+ unsigned char wakeup_tries;
+ wait_queue_head_t cmd_wait_q;
+ wait_queue_head_t event_hs_wait_q;
+ unsigned char cmd_complete;
+ bool is_suspended;
+};
+
+struct btmtk_private {
+ struct btmtk_device btmtk_dev;
+ struct btmtk_adapter *adapter;
+ struct btmtk_thread main_thread;
+ int (*hw_host_to_card)(struct sk_buff *skb);
+ int (*hci_close)(void);
+ int (*sdio_download_fw)(void);
+
+ int (*hw_set_own_back)(int owntype);
+ //int (*hw_wakeup_firmware)(struct btmtk_private *priv,int owntype);
+ int (*hw_process_int_status)(struct btmtk_private *priv);
+ void (*firmware_dump)(struct btmtk_private *priv);
+ spinlock_t driver_lock; /* spinlock used by driver */
+#ifdef CONFIG_DEBUG_FS
+ void *debugfs_data;
+#endif
+ bool surprise_removed;
+};
+
+#define VERSION "1.0"
+
+#define BT_BUFFER_SIZE 2048
+
+static struct btmtk_private *btmtk_priv;
+static unsigned char *txbuf = NULL;
+static unsigned char *rxbuf = NULL;
+static unsigned int rx_length = 0;
+static int init_flag = 0;
+
+#define PRINT_BUF(buf, len) \
+do { \
+ int i = 0; \
+ char str[128]; \
+ char *p_str; \
+ for (i = 0; i < len; i += 16) { \
+ p_str = str; \
+ if ((i + 16) <= len) { \
+ p_str += sprintf(p_str, \
+ "%s: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", \
+ __func__, \
+ buf[i], buf[i+1], buf[i+2], buf[i+3], \
+ buf[i+4], buf[i+5], buf[i+6], buf[i+7], \
+ buf[i+8], buf[i+9], buf[i+10], buf[i+11], \
+ buf[i+12], buf[i+13], buf[i+14], buf[i+15]); \
+ } else { \
+ p_str += sprintf(p_str, "%s:", __func__); \
+ for(; i < len; i++) \
+ p_str += sprintf(p_str, " %02X", buf[i]); \
+ p_str += sprintf(p_str, "\n"); \
+ } \
+ BTMTK_DBG("%s", str); \
+ } \
+}while(0)
+
+/*
+ * Reset flag for whole chip reset scenario, to indicate reset status:
+ * 0 - normal, no whole chip reset occurs
+ * 1 - reset start
+ * 2 - reset end, have not sent Hardware Error event yet
+ * 3 - reset end, already sent Hardware Error event
+ */
+//static DECLARE_WAIT_QUEUE_HEAD(BT_wq);
+static volatile UINT32 rstflag;
+static INT32 flag;
+//static UINT8 HCI_EVT_HW_ERROR[] = {0x04, 0x10, 0x01, 0x00};
+
+/*******************************************************************
+ * WHOLE CHIP RESET message handler
+ *******************************************************************
+*/
+static VOID bt_cdev_rst_cb(ENUM_WMTDRV_TYPE_T src,
+ ENUM_WMTDRV_TYPE_T dst,
+ ENUM_WMTMSG_TYPE_T type,
+ PVOID buf, UINT32 sz)
+{
+ ENUM_WMTRSTMSG_TYPE_T rst_msg;
+
+ if (sz > sizeof(ENUM_WMTRSTMSG_TYPE_T)) {
+ BTMTK_WARN("Invalid message format!\n");
+ return;
+ }
+
+ memcpy((PINT8)&rst_msg, (PINT8)buf, sz);
+ BTMTK_DBG("src = %d, dst = %d, type = %d, buf = 0x%x sz = %d, max = %d\n",
+ src, dst, type, rst_msg, sz, WMTRSTMSG_RESET_MAX);
+ if ((src == WMTDRV_TYPE_WMT) && (dst == WMTDRV_TYPE_BT) && (type == WMTMSG_TYPE_RESET)) {
+ switch (rst_msg) {
+ case WMTRSTMSG_RESET_START:
+ BTMTK_INFO("Whole chip reset start!\n");
+ rstflag = 1;
+ break;
+
+ case WMTRSTMSG_RESET_END:
+ case WMTRSTMSG_RESET_END_FAIL:
+ if (rst_msg == WMTRSTMSG_RESET_END)
+ BTMTK_INFO("Whole chip reset end!\n");
+ else
+ BTMTK_INFO("Whole chip reset fail!\n");
+ rstflag = 2;
+ flag = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+#define CFG_SUPPORT_NVRAM 1
+#if CFG_SUPPORT_NVRAM
+#define BT_NVRAM_FILE_NAME "/data/nvram/APCFG/APRDEB/BT_Addr"
+// the record structure define of bt nvram file
+typedef struct
+{
+ unsigned char addr[6]; // BT address
+ unsigned char Voice[2]; // Voice setting for SCO connection
+ unsigned char Codec[4]; // PCM codec setting
+ unsigned char Radio[6]; // RF configuration
+ unsigned char Sleep[7]; // Sleep mode configuration
+ unsigned char BtFTR[2]; // Other feature setting
+ unsigned char TxPWOffset[3]; // TX power channel offset compensation
+ unsigned char CoexAdjust[6]; // BT/WIFI coexistence performance adjustment
+}ap_nvram_btradio_struct;
+
+
+static ap_nvram_btradio_struct stBtDefault =
+{
+ {0x00, 0x00, 0x46, 0x81, 0x67, 0x01},
+ {0x60, 0x00}, //not used
+#if defined(MTK_MERGE_INTERFACE_SUPPORT)
+ {0x63, 0x10, 0x00, 0x00},
+#elif defined(__MTK_BT_I2S_SUPPORT__)
+ {0x03, 0x10, 0x00, 0x02},
+#else
+ {0x23, 0x10, 0x00, 0x00},
+#endif
+ {0x07, 0x80, 0x00, 0x06, 0x05, 0x07},
+ {0x03, 0x40, 0x1F, 0x40, 0x1F, 0x00, 0x04},
+ {0x80, 0x00}, //not used
+ {0xFF, 0xFF, 0xFF},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+};
+
+static bool cfg_flag;
+static bool rcv_flag;
+static char opcode[2];
+static unsigned char rx_evt[16];
+
+typedef int (*HCI_CMD_FUNC_T)(ap_nvram_btradio_struct *bt_nvm);
+typedef struct {
+ HCI_CMD_FUNC_T command_func;
+}HCI_SEQ_T;
+
+static int btmtk_fw_cfg_set_check(char *cmd, int cmd_len,
+ char *event, int event_len)
+{
+ int retrytime = 60;
+ char str[128];
+ char *p_str;
+ int i;
+
+ PRINT_BUF(cmd, (cmd_len < 16 ? cmd_len : 16));
+
+ if (mtk_wcn_stp_send_data(cmd, cmd_len, BT_TASK_INDX) < 0) {
+ memset(str, 0, sizeof(str));
+ p_str = str;
+ p_str += sprintf(p_str, "%02X", cmd[0]);
+ for (i = 1; i < cmd_len; i++)
+ p_str += sprintf(p_str, " %02X", cmd[i]);
+ BTMTK_ERR("%s: send fail(%s)\n", __func__, str);
+ return -EIO;
+ }
+
+ if (event && (event_len != 0)) {
+ rcv_flag = false;
+ opcode[0] = cmd[1];
+ opcode[1] = cmd[2];
+
+ do {
+ if (rcv_flag == true)
+ break;
+
+ if (retrytime == 0) {
+ BTMTK_ERR("%s: recv %02X%02X fail\n", __func__,
+ cmd[2], cmd[1]);
+ return -EIO;
+ }
+
+ if (retrytime < 40)
+ BTMTK_WARN("%s: retry over 2s, retrytime %d\n",
+ __func__, retrytime);
+ retrytime--;
+ msleep(100);
+ } while(1);
+
+ if (memcmp(rx_evt, event, event_len) != 0) {
+ memset(str, 0, sizeof(str));
+ p_str = str;
+ p_str += sprintf(p_str, "%02X", rx_evt[0]);
+ for (i = 1; i < event_len; i++)
+ p_str += sprintf(p_str, " %02X", rx_evt[i]);
+ BTMTK_ERR("%s: check %02X%02X fail(%s)\n", __func__,
+ cmd[2], cmd[1],
+ str);
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+#ifdef BD_ADDR_AUTOGEN
+static void GetRandomValue(u8 string[6])
+{
+ int iRandom = 0;
+
+ BTMTK_INFO("Enable random generation\n");
+
+ /* first random */
+ get_random_bytes(&iRandom, sizeof(int));
+ BTMTK_INFO("iRandom = [%d]", iRandom);
+ string[0] = (((iRandom>>24|iRandom>>16) & (0xFE)) | (0x02)); /* Must use private bit(1) and no BCMC bit(0) */
+
+ /* second random */
+ get_random_bytes(&iRandom, sizeof(int));
+ BTMTK_INFO("iRandom = [%d]", iRandom);
+ string[1] = ((iRandom>>8) & 0xFF);
+
+ /* third random */
+ get_random_bytes(&iRandom, sizeof(int));
+ BTMTK_INFO("iRandom = [%d]", iRandom);
+ string[5] = (iRandom & 0xFF);
+
+ return;
+}
+#endif
+
+static int nvram_write(char *filename, char *buf, ssize_t len, int offset)
+{
+ struct file *fd;
+ int retLen = -1;
+
+ mm_segment_t old_fs = get_fs();
+
+ set_fs(KERNEL_DS);
+
+ fd = filp_open(filename, O_WRONLY, 0644);
+
+ if (IS_ERR(fd)) {
+ BTMTK_INFO("[nvram_write] : failed to open!!");
+ return -1;
+ }
+
+ fd->f_pos = offset;
+ retLen = vfs_write(fd, buf, len, &fd->f_pos);
+ filp_close(fd, NULL);
+
+ set_fs(old_fs);
+
+ return retLen;
+}
+
+int btmtk_set_cfg_to_nvram(UINT8 *buf, ssize_t len)
+{
+ return nvram_write(BT_NVRAM_FILE_NAME, buf, len, 0);
+}
+
+static int btmtk_set_local_bd_addr(ap_nvram_btradio_struct *bt_nvm)
+{
+ char cmd[] = {0x01, 0x1A, 0xFC, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ char event[] = {0x04, 0x0E, 0x04,
+ 0x01, 0x1A, 0xFC, 0x00};
+
+ cmd[4] = bt_nvm->addr[5];
+ cmd[5] = bt_nvm->addr[4];
+ cmd[6] = bt_nvm->addr[3];
+ cmd[7] = bt_nvm->addr[2];
+ cmd[8] = bt_nvm->addr[1];
+ cmd[9] = bt_nvm->addr[0];
+
+ return btmtk_fw_cfg_set_check(cmd, sizeof(cmd), event, sizeof(event));
+}
+
+static int btmtk_set_radio(ap_nvram_btradio_struct *bt_nvm)
+{
+ char cmd[] = {0x01, 0x79, 0xFC, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ char event[] = {0x04, 0x0E, 0x04,
+ 0x01, 0x79, 0xFC, 0x00};
+
+ cmd[4] = bt_nvm->Radio[0];
+ cmd[5] = bt_nvm->Radio[1];
+ cmd[6] = bt_nvm->Radio[2];
+ cmd[7] = bt_nvm->Radio[3];
+ cmd[8] = bt_nvm->Radio[4];
+ cmd[9] = bt_nvm->Radio[5];
+
+ return btmtk_fw_cfg_set_check(cmd, sizeof(cmd), event, sizeof(event));
+}
+
+static int btmtk_set_tx_power_offset(ap_nvram_btradio_struct *bt_nvm)
+{
+ char cmd[] = {0x01, 0x93, 0xFC, 0x03,
+ 0x00, 0x00, 0x00};
+ char event[] = {0x04, 0x0E, 0x04,
+ 0x01, 0x93, 0xFC, 0x00};
+
+ cmd[4] = bt_nvm->TxPWOffset[0];
+ cmd[5] = bt_nvm->TxPWOffset[1];
+ cmd[6] = bt_nvm->TxPWOffset[2];
+
+ return btmtk_fw_cfg_set_check(cmd, sizeof(cmd), event, sizeof(event));
+}
+
+static int btmtk_set_sleep_timeout(ap_nvram_btradio_struct *bt_nvm)
+{
+ char cmd[] = {0x01, 0x7A, 0xFC, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ char event[] = {0x04, 0x0E, 0x04,
+ 0x01, 0x7A, 0xFC, 0x00};
+
+ cmd[4] = bt_nvm->Sleep[0];
+ cmd[5] = bt_nvm->Sleep[1];
+ cmd[6] = bt_nvm->Sleep[2];
+ cmd[7] = bt_nvm->Sleep[3];
+ cmd[8] = bt_nvm->Sleep[4];
+ cmd[9] = bt_nvm->Sleep[5];
+ cmd[10] = bt_nvm->Sleep[6];
+
+ return btmtk_fw_cfg_set_check(cmd, sizeof(cmd), event, sizeof(event));
+}
+
+static int btmtk_reset(ap_nvram_btradio_struct *bt_nvm)
+{
+ char cmd[] = {0x01, 0x03, 0x0C, 0x00};
+ char event[] = {0x04, 0x0E, 0x04,
+ 0x01, 0x03, 0x0C, 0x00};
+
+ return btmtk_fw_cfg_set_check(cmd, sizeof(cmd), event, sizeof(event));
+}
+
+HCI_SEQ_T bt_init_preload_script[] =
+{
+ { btmtk_set_local_bd_addr }, /*0xFC1A*/
+ { btmtk_set_radio }, /*0xFC79*/
+ { btmtk_set_tx_power_offset }, /*0xFC93*/
+ { btmtk_set_sleep_timeout }, /*0xFC7A*/
+ { btmtk_reset }, /*0x0C03*/
+ { 0 },
+};
+
+/*----------------------------------------------------------------------------*/
+/*!
+* \brief Utility function for reading data from files on NVRAM-FS
+*
+* \param[in]
+* filename
+* len
+* offset
+* \param[out]
+* buf
+* \return
+* actual length of data being read
+*/
+/*----------------------------------------------------------------------------*/
+static int nvram_read(char *filename, char *buf, ssize_t len, int offset)
+{
+ struct file *fd;
+ int retLen = -1;
+
+ mm_segment_t old_fs = get_fs();
+
+ set_fs(KERNEL_DS);
+
+ fd = filp_open(filename, O_RDONLY, 0644);
+
+ if (IS_ERR(fd)) {
+ BTMTK_INFO("[nvram_read] : failed to open!!");
+ return -1;
+ }
+#if 0
+ do {
+ if ((fd->f_op == NULL) || (fd->f_op->read == NULL)) {
+ BTMTK_INFO("[nvram_read] : file can not be read!!\n");
+ break;
+ }
+
+ if (fd->f_pos != offset) {
+ if (fd->f_op->llseek) {
+ if (fd->f_op->llseek(fd, offset, 0) != offset) {
+ BTMTK_INFO("[nvram_read] : failed to seek!!\n");
+ break;
+ }
+ } else {
+ fd->f_pos = offset;
+ }
+ }
+
+ retLen = fd->f_op->read(fd, buf, len, &fd->f_pos);
+
+ } while (false);
+#else
+ fd->f_pos = offset;
+ retLen = vfs_read(fd, buf, len, &fd->f_pos);
+#endif
+ filp_close(fd, NULL);
+
+ set_fs(old_fs);
+
+ return retLen;
+}
+
+int btmtk_get_cfg_from_nvram(UINT8 *buf, ssize_t len)
+{
+ return nvram_read(BT_NVRAM_FILE_NAME, buf, len, 0);
+}
+
+void btmtk_fw_cfg(void)
+{
+ ap_nvram_btradio_struct bt_nvm;
+ int i = 0;
+#ifdef BD_ADDR_AUTOGEN
+ UINT8 ucZeroAddr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+#endif
+
+ if (btmtk_get_cfg_from_nvram((UINT8 *)&bt_nvm, sizeof(bt_nvm)) <= 0) {
+ BTMTK_ERR("%s: get configuration failed!\n", __func__);
+ return;
+ }
+ BTMTK_INFO("[BDAddr %02x-%02x-%02x-%02x-%02x-%02x]",
+ bt_nvm.addr[0], bt_nvm.addr[1], bt_nvm.addr[2], bt_nvm.addr[3], bt_nvm.addr[4], bt_nvm.addr[5]);
+ BTMTK_INFO("[Voice %02x %02x]",
+ bt_nvm.Voice[0], bt_nvm.Voice[1]);
+ BTMTK_INFO("[Codec %02x %02x %02x %02x]",
+ bt_nvm.Codec[0], bt_nvm.Codec[1], bt_nvm.Codec[2], bt_nvm.Codec[3]);
+ BTMTK_INFO("[Radio %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.Radio[0], bt_nvm.Radio[1], bt_nvm.Radio[2], bt_nvm.Radio[3], bt_nvm.Radio[4], bt_nvm.Radio[5]);
+ BTMTK_INFO("[Sleep %02x %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.Sleep[0], bt_nvm.Sleep[1], bt_nvm.Sleep[2], bt_nvm.Sleep[3], bt_nvm.Sleep[4], bt_nvm.Sleep[5], bt_nvm.Sleep[6]);
+ BTMTK_INFO("[BtFTR %02x %02x]",
+ bt_nvm.BtFTR[0], bt_nvm.BtFTR[1]);
+ BTMTK_INFO("[TxPWOffset %02x %02x %02x]",
+ bt_nvm.TxPWOffset[0], bt_nvm.TxPWOffset[1], bt_nvm.TxPWOffset[2]);
+ BTMTK_INFO("[CoexAdjust %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.CoexAdjust[0], bt_nvm.CoexAdjust[1], bt_nvm.CoexAdjust[2], bt_nvm.CoexAdjust[3], bt_nvm.CoexAdjust[4], bt_nvm.CoexAdjust[5]);
+
+ cfg_flag = true;
+
+#ifdef BD_ADDR_AUTOGEN
+ if ((0 == memcmp(bt_nvm.addr, stBtDefault.addr, 6)) ||
+ (0 == memcmp(bt_nvm.addr, ucZeroAddr, 6))) {
+ GetRandomValue(bt_nvm.addr);
+ if (btmtk_set_cfg_to_nvram((UINT8 *)&bt_nvm, sizeof(bt_nvm)) <= 0) {
+ BTMTK_ERR("%s: set configuration failed!\n", __func__);
+ return;
+ }
+#if 0
+ if (btmtk_get_cfg_from_nvram((UINT8 *)&bt_nvm, sizeof(bt_nvm)) <= 0) {
+ BTMTK_ERR("%s: get configuration failed!\n", __func__);
+ return;
+ }
+ BTMTK_DBG("after read");
+ BTMTK_DBG("[BDAddr %02x-%02x-%02x-%02x-%02x-%02x]",
+ bt_nvm.addr[0], bt_nvm.addr[1], bt_nvm.addr[2], bt_nvm.addr[3], bt_nvm.addr[4], bt_nvm.addr[5]);
+ BTMTK_DBG("[Voice %02x %02x]",
+ bt_nvm.Voice[0], bt_nvm.Voice[1]);
+ BTMTK_DBG("[Codec %02x %02x %02x %02x]",
+ bt_nvm.Codec[0], bt_nvm.Codec[1], bt_nvm.Codec[2], bt_nvm.Codec[3]);
+ BTMTK_DBG("[Radio %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.Radio[0], bt_nvm.Radio[1], bt_nvm.Radio[2], bt_nvm.Radio[3], bt_nvm.Radio[4], bt_nvm.Radio[5]);
+ BTMTK_DBG("[Sleep %02x %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.Sleep[0], bt_nvm.Sleep[1], bt_nvm.Sleep[2], bt_nvm.Sleep[3], bt_nvm.Sleep[4], bt_nvm.Sleep[5], bt_nvm.Sleep[6]);
+ BTMTK_DBG("[BtFTR %02x %02x]",
+ bt_nvm.BtFTR[0], bt_nvm.BtFTR[1]);
+ BTMTK_DBG("[TxPWOffset %02x %02x %02x]",
+ bt_nvm.TxPWOffset[0], bt_nvm.TxPWOffset[1], bt_nvm.TxPWOffset[2]);
+ BTMTK_DBG("[CoexAdjust %02x %02x %02x %02x %02x %02x]",
+ bt_nvm.CoexAdjust[0], bt_nvm.CoexAdjust[1], bt_nvm.CoexAdjust[2], bt_nvm.CoexAdjust[3], bt_nvm.CoexAdjust[4], bt_nvm.CoexAdjust[5]);
+#endif
+ }
+#endif /* BD_ADDR_AUTOGEN */
+
+ while(bt_init_preload_script[i].command_func) {
+ if (bt_init_preload_script[i].command_func(&bt_nvm) < 0)
+ BTMTK_ERR("%s: set fw failed(%d)!", __func__, i);
+ i++;
+ }
+
+ cfg_flag = false;
+}
+#endif
+
+static int btmtk_sdio_host_to_card(struct sk_buff *skb)
+{
+ int ret = 0;
+ int len = 0;
+
+ if (!skb) {
+ BTMTK_WARN("%s skb is NULL return -EINVAL", __func__);
+ return -EINVAL;
+ }
+
+ if (!skb->data) {
+ BTMTK_WARN("%s skb->data is NULL return -EINVAL", __func__);
+ return -EINVAL;
+ }
+
+ if (!skb->len || (skb->len > BT_BUFFER_SIZE)) {
+ BTMTK_WARN("%s Tx Error: Bad skb length %d : %d", __func__,
+ skb->len, BT_BUFFER_SIZE);
+ return -EINVAL;
+ }
+
+ txbuf[0] = bt_cb(skb)->pkt_type;
+ memcpy(&txbuf[1], &skb->data[0], skb->len);
+ len = skb->len + 1;
+ kfree_skb(skb);
+ PRINT_BUF(txbuf, (len < 16 ? len : 16));
+
+ ret = mtk_wcn_stp_send_data(txbuf, len, BT_TASK_INDX);
+
+ return ret;
+}
+
+static int btmtk_sdio_card_to_host(struct btmtk_private *priv)
+{
+ struct sk_buff *skb = NULL;
+ struct hci_dev *hdev;
+ unsigned char *buf;
+ int type;
+ int buf_len, copy_len, hdr_len, payload_len;
+ int err = 0;
+
+ PRINT_BUF(rxbuf, (rx_length < 16 ? rx_length : 16));
+
+ hdev = priv->btmtk_dev.hcidev;
+
+ type = rxbuf[0];
+ buf = rxbuf + 1;
+ buf_len = rx_length - 1;
+
+ switch (type) {
+ case HCI_ACLDATA_PKT:
+ hdr_len = HCI_ACL_HDR_SIZE;
+ payload_len = rxbuf[3] | (rxbuf[4] << 8);
+ break;
+ case HCI_SCODATA_PKT:
+ hdr_len = HCI_SCO_HDR_SIZE;
+ payload_len = rxbuf[3];
+ break;
+ case HCI_EVENT_PKT:
+ hdr_len = HCI_EVENT_HDR_SIZE;
+ payload_len = rxbuf[2];
+ if (rxbuf[4] == 0x04 && rxbuf[5] == 0x10 && rxbuf[6] != 0) {
+ BTMTK_INFO("%s opcode 0x1004, status 0x%x -> 0", __func__, rxbuf[6]);
+ rxbuf[6] = 0;
+ }
+ break;
+ default:
+ BTMTK_WARN("%s Unknown packet type:%d", __func__, type);
+ err = -1;
+ goto exit;
+ }
+
+ while (buf_len) {
+ if (!skb) {
+ skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ err = -ENOMEM;
+ break;
+ }
+ bt_cb(skb)->pkt_type = type;
+ bt_cb(skb)->expect = hdr_len;
+ }
+
+ copy_len = min_t(int, bt_cb(skb)->expect, buf_len);
+ memcpy(skb_put(skb, copy_len), buf, copy_len);
+
+ buf += copy_len;
+ buf_len -= copy_len;
+ bt_cb(skb)->expect -= copy_len;
+
+ if (skb->len == hdr_len) {
+ /* Complete header */
+ bt_cb(skb)->expect = payload_len;
+
+ if (skb_tailroom(skb) < bt_cb(skb)->expect) {
+ kfree_skb(skb);
+ skb = NULL;
+ err = -EILSEQ;
+ break;
+ }
+ }
+
+ if (bt_cb(skb)->expect == 0) {
+ /* Complete frame */
+ hci_recv_frame(hdev, skb);
+ skb = NULL;
+ }
+ }
+
+exit:
+ if (err) {
+ BTMTK_DBG("%s fail free skb\n", __func__);
+ hdev->stat.err_rx++;
+ if (skb)
+ kfree_skb(skb);
+ }
+
+ return err;
+}
+
+static VOID btmtk_recv_cb(const PUINT8 data, INT32 size)
+{
+ if (size) {
+ memcpy(rxbuf, data, size);
+ rx_length = size;
+
+#if CFG_SUPPORT_NVRAM
+ if (rcv_flag == false && opcode[0] == rxbuf[4] && opcode[1] == rxbuf[5]) {
+ PRINT_BUF(rxbuf, (rx_length < 16 ? rx_length : 16));
+ memcpy(rx_evt, rxbuf, (rx_length < 16 ? rx_length : 16));
+ rcv_flag = true;
+ } else {
+#endif
+ btmtk_sdio_card_to_host(btmtk_priv);
+#if CFG_SUPPORT_NVRAM
+ }
+#endif
+ }
+
+ return;
+}
+
+static int btmtk_open(struct hci_dev *hdev)
+{
+ /* Turn on BT */
+#if 0
+ if (mtk_wcn_wmt_func_on(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
+ BTMTK_WARN("WMT turn on BT fail!\n");
+ return -EIO;
+ }
+#else
+ while (mtk_wcn_wmt_func_on(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
+ int cnt = 0;
+ BTMTK_WARN("WMT turn on BT fail!, retry %d\n", cnt);
+ cnt++;
+ msleep(1000);
+ }
+#endif
+
+ BTMTK_INFO("WMT turn on BT OK!\n");
+ rstflag = 0;
+
+ if (mtk_wcn_stp_is_ready()) {
+
+ mtk_wcn_stp_set_bluez(1);
+ mtk_wcn_stp_register_if_rx(btmtk_recv_cb);
+ BTMTK_INFO("Now it's in MTK Bluetooth Mode\n");
+ BTMTK_INFO("STP is ready!\n");
+
+ BTMTK_DBG("Register BT event callback!\n");
+ mtk_wcn_stp_register_event_cb(BT_TASK_INDX, NULL);
+ } else {
+ BTMTK_ERR("STP is not ready!\n");
+ mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT);
+ return -EIO;
+ }
+
+ BTMTK_DBG("Register BT reset callback!\n");
+ mtk_wcn_wmt_msgcb_reg(WMTDRV_TYPE_BT, bt_cdev_rst_cb);
+
+ BTMTK_INFO("%s set HCI_RUNNIN %08x\n",__func__, HCI_RUNNING);
+ set_bit(HCI_RUNNING, &hdev->flags);
+
+#if CFG_SUPPORT_NVRAM
+ btmtk_fw_cfg();
+#endif
+ return 0;
+}
+
+static int btmtk_close(struct hci_dev *hdev)
+{
+ BTMTK_INFO("%s \n",__func__);
+ if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) {
+ BTMTK_WARN("%s is not hci running\n",__func__);
+ //return 0;
+ }
+
+ rstflag = 0;
+ mtk_wcn_wmt_msgcb_unreg(WMTDRV_TYPE_BT);
+ mtk_wcn_stp_register_event_cb(BT_TASK_INDX, NULL);
+
+ if (mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
+ BTMTK_ERR("WMT turn off BT fail!\n");
+ /* Mostly, native program will not check this return value. */
+ return -EIO;
+ }
+
+ BTMTK_INFO("WMT turn off BT OK!\n");
+ return 0;
+}
+static int btmtk_flush(struct hci_dev *hdev)
+{
+ //struct btmtk_private *priv = hci_get_drvdata(hdev);
+
+ //skb_queue_purge(&priv->adapter->tx_queue);
+
+ return 0;
+}
+
+static int btmtk_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ if (!hdev) {
+ BTMTK_INFO("%s hdev=NULL return\n",__func__);
+ return -ENODEV;
+ }
+
+ if (!test_bit(HCI_RUNNING, &hdev->flags)) {
+ BTMTK_ERR("%s Failed testing HCI_RUNING, flags=%lx\n",
+ __func__, hdev->flags);
+ BTMTK_INFO("%s return -EBUSY\n",__func__);
+ return -EBUSY;
+ }
+
+ BTMTK_DBG("%s type=%d, len=%d\n",__func__, bt_cb(skb)->pkt_type, skb->len);
+
+ switch (bt_cb(skb)->pkt_type) {
+ case HCI_COMMAND_PKT:
+ hdev->stat.cmd_tx++;
+ break;
+ case HCI_ACLDATA_PKT:
+ hdev->stat.acl_tx++;
+ break;
+ case HCI_SCODATA_PKT:
+ hdev->stat.sco_tx++;
+ break;
+ }
+
+ btmtk_sdio_host_to_card(skb);
+ return 0;
+}
+
+static int btmtk_setup(struct hci_dev *hdev)
+{
+ BTMTK_INFO("%s \n",__func__);
+
+ BTMTK_INFO("%s set HCI_RUNNIN %08x\n", __func__, HCI_RUNNING);
+ set_bit(HCI_RUNNING, &hdev->flags);
+
+ if (!test_bit(HCI_RUNNING, &hdev->flags))
+ BTMTK_ERR("%s Failed testing HCI_RUNING, flags=%lx",
+ __func__, hdev->flags);
+ return 0;
+}
+
+static int btmtk_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr)
+{
+ unsigned char set_bdaddr[] = {0x01, 0x1A, 0xFC, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ int ret = 0;
+
+ memcpy(&set_bdaddr[4], &bdaddr->b[0], 6);
+
+ PRINT_BUF(set_bdaddr, (sizeof(set_bdaddr) < 16 ? sizeof(set_bdaddr) : 16));
+
+ ret = mtk_wcn_stp_send_data(set_bdaddr, sizeof(set_bdaddr), BT_TASK_INDX);
+ if (ret < 0)
+ BTMTK_ERR("Set BD addr failed!");
+ return ret;
+}
+
+struct btmtk_private *btmtk_add_card(void)
+{
+ struct btmtk_private *priv;
+ BTMTK_INFO("%s -->", __func__);
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ BTMTK_ERR("Can not allocate priv");
+
+ return priv;
+}
+
+int btmtk_remove_card(struct btmtk_private *priv)
+{
+ struct hci_dev *hdev;
+
+ BTMTK_INFO("%s \n",__func__);
+ hdev = priv->btmtk_dev.hcidev;
+
+ hci_unregister_dev(hdev);
+
+ hci_free_dev(hdev);
+
+ priv->btmtk_dev.hcidev = NULL;
+
+ kfree(priv);
+
+ return 0;
+}
+
+int btmtk_register_hdev(struct btmtk_private *priv)
+{
+ struct hci_dev *hdev = NULL;
+ int ret;
+
+ BTMTK_INFO("%s \n",__func__);
+ hdev = hci_alloc_dev();
+ if (!hdev) {
+ BTMTK_ERR("Can not allocate HCI device");
+ goto err_hdev;
+ }
+
+ priv->btmtk_dev.hcidev = hdev;
+ hci_set_drvdata(hdev, priv);
+
+ hdev->bus = HCI_SDIO;
+ hdev->open = btmtk_open;
+ hdev->close = btmtk_close;
+ hdev->flush = btmtk_flush;
+ hdev->send = btmtk_send_frame;
+ hdev->setup = btmtk_setup;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0))
+ hdev->set_bdaddr = btmtk_set_bdaddr;
+#endif
+ hdev->dev_type = priv->btmtk_dev.dev_type;
+
+ ret = hci_register_dev(hdev);
+ if (ret < 0) {
+ BTMTK_ERR("Can not register HCI device");
+ goto err_hci_register_dev;
+ }
+
+ return 0;
+
+err_hci_register_dev:
+ hci_free_dev(hdev);
+
+err_hdev:
+ kfree(priv);
+
+ return -ENOMEM;
+}
+
+static int BT_init(void)
+{
+ if (init_flag == 1) {
+ BTMTK_INFO("Already initialized");
+ return 0;
+ }
+
+ BTMTK_INFO();
+#if CFG_SUPPORT_NVRAM
+ cfg_flag = false;
+#endif
+ if (txbuf == NULL) {
+ txbuf = kmalloc(BT_BUFFER_SIZE, GFP_ATOMIC);
+ memset(txbuf, 0, BT_BUFFER_SIZE);
+ }
+
+ if (rxbuf == NULL) {
+ rxbuf = kmalloc(BT_BUFFER_SIZE, GFP_ATOMIC);
+ memset(rxbuf, 0, BT_BUFFER_SIZE);
+ }
+
+ btmtk_priv = btmtk_add_card();
+ if (!btmtk_priv) {
+ BTMTK_ERR("Initializing card failed!");
+ kfree(txbuf);
+ kfree(rxbuf);
+ return -ENODEV;
+ }
+
+ btmtk_register_hdev(btmtk_priv);
+
+ init_flag = 1;
+
+ return 0;
+}
+
+static void BT_exit(void)
+{
+ if (init_flag != 1) {
+ BTMTK_INFO("No initialize");
+ return;
+ }
+
+ btmtk_remove_card(btmtk_priv);
+ kfree(txbuf);
+ kfree(rxbuf);
+ init_flag = 0;
+ BTMTK_INFO("%s driver removed\n", BT_DRIVER_NAME);
+}
+
+#ifdef MTK_WCN_REMOVE_KERNEL_MODULE
+int mtk_wcn_stpbt_drv_init(void)
+{
+ return BT_init();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_init);
+
+void mtk_wcn_stpbt_drv_exit(void)
+{
+ return BT_exit();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_exit);
+
+#else
+
+module_init(BT_init);
+module_exit(BT_exit);
+
+#endif