Project import generated by Copybara.
GitOrigin-RevId: bb02acc694a4e1dc411c14a022f146d731f94227
diff --git a/.checkpatch.conf b/.checkpatch.conf
new file mode 100644
index 0000000..c8df01f
--- /dev/null
+++ b/.checkpatch.conf
@@ -0,0 +1,5 @@
+--no-tree
+--show-types
+--max-line-length=120
+--no-signoff
+--ignore=GERRIT_CHANGE_ID
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..ffa3eda
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,91 @@
+# Copyright Statement:
+#
+# This software/firmware and related documentation ("MediaTek Software") are
+# protected under relevant copyright laws. The information contained herein
+# is confidential and proprietary to MediaTek Inc. and/or its licensors.
+# Without the prior written permission of MediaTek inc. and/or its licensors,
+# any reproduction, modification, use or disclosure of MediaTek Software,
+# and information contained herein, in whole or in part, shall be strictly prohibited.
+#
+# MediaTek Inc. (C) 2017. All rights reserved.
+#
+# BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+# THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+# RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
+# AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+# NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+# SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+# SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
+# THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
+# THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
+# CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
+# SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
+# STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
+# CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+# AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+# OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
+# MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+# The following software/firmware and/or related documentation ("MediaTek Software")
+# have been modified by MediaTek Inc. All revisions are subject to any receiver's
+# applicable license agreements with MediaTek Inc.
+
+###############################################################################
+# Generally Android.mk can not get KConfig setting
+# we can use this way to get
+# include the final KConfig
+# but there is no $(AUTO_CONF) at the first time (no out folder) when make
+#
+#ifneq (,$(wildcard $(AUTO_CONF)))
+#include $(AUTO_CONF)
+#include $(CLEAR_VARS)
+#endif
+
+###############################################################################
+###############################################################################
+# Generally Android.mk can not get KConfig setting #
+# #
+# do not have any KConfig checking in Android.mk #
+# do not have any KConfig checking in Android.mk #
+# do not have any KConfig checking in Android.mk #
+# #
+# e.g. ifeq ($(CONFIG_MTK_COMBO_WIFI), m) #
+# xxxx #
+# endif #
+# #
+# e.g. ifneq ($(filter "MT6632",$(CONFIG_MTK_COMBO_CHIP)),) #
+# xxxx #
+# endif #
+# #
+# All the KConfig checking should move to Makefile for each module #
+# All the KConfig checking should move to Makefile for each module #
+# All the KConfig checking should move to Makefile for each module #
+# #
+###############################################################################
+###############################################################################
+
+LOCAL_PATH := $(call my-dir)
+
+BT_PLATFORM:=$(subst MTK_CONSYS_MT,,$(MTK_BT_CHIP))
+$(info [BT_Drv] MTK_BT_SUPPORT = $(MTK_BT_SUPPORT))
+$(info [BT_Drv] MTK_BT_CHIP = $(MTK_BT_CHIP))
+
+ifeq ($(MTK_BT_SUPPORT),yes)
+#ifneq ($(filter MTK_MT79%, $(MTK_BT_CHIP)),)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := btmtk_uart_unify.ko
+LOCAL_REQUIRED_MODULES := conninfra.ko
+LOCAL_REQUIRED_MODULES += connfem.ko
+LOCAL_PROPRIETARY_MODULE := true
+LOCAL_MODULE_OWNER := mtk
+LOCAL_INIT_RC := init.btmtk_uart.rc
+LOCAL_SRC_FILES := $(patsubst $(LOCAL_PATH)/%,%,$(shell find $(LOCAL_PATH) -type f -name '*.[cho]')) Makefile
+
+include $(MTK_KERNEL_MODULE)
+
+#endif
+endif
+include $(LOCAL_PATH)/uart_daemon/Android.mk
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9c40166
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,187 @@
+export KERNEL_SRC := /lib/modules/$(shell uname -r)/build
+#################### Configurations ####################
+# Compile Options for bt driver configuration.
+CONFIG_SUPPORT_BT_DL_WIFI_PATCH=y
+CONFIG_SUPPORT_BT_DL_ZB_PATCH=y
+CONFIG_SUPPORT_BLUEZ=n
+CONFIG_SUPPORT_DVT=n
+CONFIG_SUPPORT_HW_DVT=n
+CONFIG_SUPPORT_MULTI_DEV_NODE=n
+
+ifndef TOP
+ TOP := $(srctree)/..
+endif
+
+ifneq ($(TARGET_BUILD_VARIANT), user)
+ ccflags-y += -DBUILD_QA_DBG=1
+else
+ ccflags-y += -DBUILD_QA_DBG=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_BT_DL_WIFI_PATCH), y)
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=1
+else
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_BT_DL_ZB_PATCH), y)
+ ccflags-y += -DCFG_SUPPORT_BT_DL_ZB_PATCH=1
+else
+ ccflags-y += -DCFG_SUPPORT_BT_DL_ZB_PATCH=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_BLUEZ), y)
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=1
+else
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_HW_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=0
+endif
+
+ifeq ($(SUPPORT_WAKEUP_IRQ), yes)
+ ccflags-y += -DCFG_SUPPORT_WAKEUP_IRQ
+endif
+
+ifeq ($(CONFIG_SUPPORT_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_DVT=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_MULTI_DEV_NODE), y)
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=1
+else
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=0
+endif
+
+#################### Configurations ####################
+# For chip interface, driver supports "usb", "sdio", "uart_tty", "uart_serdev" and "btif"
+MTK_CHIP_IF := uart_tty
+
+ifeq ($(MTK_CHIP_IF), sdio)
+ MOD_NAME = btmtk_sdio_unify
+ CFILES := sdio/btmtksdio.c btmtk_woble.c btmtk_buffer_mode.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_SDIO
+ ccflags-y += -DSDIO_DEBUG=0
+ ccflags-y += -I$(src)/include/sdio
+else ifeq ($(MTK_CHIP_IF), usb)
+ MOD_NAME = btmtk_usb_unify
+ CFILES := usb/btmtkusb.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_USB
+ ccflags-y += -I$(src)/include/usb
+else ifeq ($(MTK_CHIP_IF), uart_tty)
+ MOD_NAME = btmtk_uart_unify
+ CONFIG_SUPPORT_DEVICE_NODE = y
+ CFILES := uart/btmtktty.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_UART_TTY
+ ccflags-y += -I$(src)/include/uart/tty
+else ifeq ($(MTK_CHIP_IF), uart_serdev)
+ MOD_NAME = btmtk_uart_unify
+ ccflags-y += -DCHIP_IF_UART_SERDEV
+ CFILES := uart/btmtkserdev.c
+ ccflags-y += -I$(src)/include/uart/serdev
+else
+ MOD_NAME = btmtk_btif_unify
+ CFILES := btif/btmtk_btif.c
+ ccflags-y += -DCHIP_IF_BTIF
+ ccflags-y += -I$(src)/include/btif
+endif
+
+MTK_PROJ_TYPE := ce
+
+ifeq ($(CONFIG_SUPPORT_DEVICE_NODE), y)
+CFILES += btmtk_queue.c btmtk_char_dev.c
+endif
+
+ifeq ($(CONFIG_SUPPORT_DEVICE_NODE), y)
+ccflags-y += -DUSE_DEVICE_NODE=1
+ccflags-y += -DSLEEP_ENABLE=1
+else
+ccflags-y += -DUSE_DEVICE_NODE=0
+ccflags-y += -DSLEEP_ENABLE=0
+endif
+
+$(info [BT_Drv] MTK_PROJ_TYPE = $(MTK_PROJ_TYPE) src = $(src))
+#ifeq ($(MTK_PROJ_TYPE), sp)
+ifeq ($(CONFIG_SUPPORT_DEVICE_NODE), y)
+ CFILES += proj/btmtk_proj_sp.c proj/btmtk_proj_sp_debug.c
+ ccflags-y += -DANDROID_OS
+ ccflags-y += -I$(srctree)/drivers/misc/mediatek/connectivity/power_throttling
+#else ifeq ($(MTK_PROJ_TYPE), ce)
+else
+ CFILES += proj/btmtk_proj_ce.c
+ ccflags-y += -DLINUX_OS
+endif
+
+CFILES += btmtk_main.c btmtk_fw_log.c
+
+ccflags-y += -I$(src)/include/ -I$(KERNEL_SRC)/include/ -I$(KERNEL_SRC)/drivers/bluetooth -I$(src)/proj/include/
+
+ifeq ($(CONFIG_SUPPORT_DEVICE_NODE), y)
+ $(info [BT_Drv] TOP = $(TOP))
+ $(info [BT_Drv] KBUILD_EXTRA_SYMBOLS = $(KBUILD_EXTRA_SYMBOLS))
+ #KBUILD_EXTRA_SYMBOLS := /mfs/mtkslt1121/mtk24223/CAS_REAL/alps-dev-s0_bsp-connac3-1210--2022_03_08_12_00/merged/out_krn/target/product/mgk_64_k510/obj/ETC/conninfra.ko_intermediates/LINKED/Module.symvers
+ CONN_INFRA_SRC := $(TOP)/vendor/mediatek/kernel_modules/connectivity/conninfra
+ CONN_FEM_SRC := $(TOP)/vendor/mediatek/kernel_modules/connectivity/connfem
+ ccflags-y += -I$(CONN_INFRA_SRC)/include
+ ccflags-y += -I$(CONN_INFRA_SRC)/conn_drv/connv3/debug_utility
+ ccflags-y += -I$(CONN_INFRA_SRC)/conn_drv/connv3/debug_utility/include
+ ccflags-y += -I$(CONN_INFRA_SRC)/conn_drv/connv3/debug_utility/connsyslog
+ ccflags-y += -I$(CONN_INFRA_SRC)/conn_drv/connv3/debug_utility/coredump
+ ccflags-y += -I$(CONN_FEM_SRC)/include
+endif
+
+ccflags-y += -Werror
+
+$(MOD_NAME)-objs := $(CFILES:.c=.o)
+
+obj-m += $(MOD_NAME).o
+
+ifneq ($(TARGET_BUILD_VARIANT), user)
+ccflags-y += -DBTMTK_DEBUG_SOP
+endif
+
+#VPATH = /opt/toolchains/gcc-linaro-aarch64-linux-gnu-4.9-2014.09_linux
+#UART_MOD_NAME = btmtk_uart
+#UART_CFILES := \
+# btmtk_uart_main.c
+#$(UART_MOD_NAME)-objs := $(UART_CFILES:.c=.o)
+###############################################################################
+# Common
+###############################################################################
+#obj-m := $(UART_MOD_NAME).o
+all:
+ make -C $(KERNEL_SRC) M=$(PWD) modules
+clean:
+ make -C $(KERNEL_SRC) M=$(PWD) clean
+# Check coding style
+# export IGNORE_CODING_STYLE_RULES := NEW_TYPEDEFS,LEADING_SPACE,CODE_INDENT,SUSPECT_CODE_INDENT
+ccs:
+ ./util/checkpatch.pl -f ./sdio/btmtksdio.c
+ ./util/checkpatch.pl -f ./include/sdio/btmtk_sdio.h
+ ./util/checkpatch.pl -f ./include/btmtk_define.h
+ ./util/checkpatch.pl -f ./include/btmtk_drv.h
+ ./util/checkpatch.pl -f ./include/btmtk_chip_if.h
+ ./util/checkpatch.pl -f ./include/btmtk_main.h
+ ./util/checkpatch.pl -f ./include/btmtk_buffer_mode.h
+ ./util/checkpatch.pl -f ./include/uart/tty/btmtk_uart_tty.h
+ ./util/checkpatch.pl -f ./uart/btmtktty.c
+ ./util/checkpatch.pl -f ./include/btmtk_fw_log.h
+ ./util/checkpatch.pl -f ./include/btmtk_woble.h
+ ./util/checkpatch.pl -f ./include/uart/btmtk_uart.h
+ ./util/checkpatch.pl -f ./uart/btmtk_uart_main.c
+ ./util/checkpatch.pl -f ./include/usb/btmtk_usb.h
+ ./util/checkpatch.pl -f ./usb/btmtkusb.c
+ ./util/checkpatch.pl -f ./proj/btmtk_proj_ce.c
+ ./util/checkpatch.pl -f ./proj/btmtk_proj_sp.c
+ ./util/checkpatch.pl -f btmtk_fw_log.c
+ ./util/checkpatch.pl -f btmtk_main.c
+ ./util/checkpatch.pl -f btmtk_buffer_mode.c
+ ./util/checkpatch.pl -f btmtk_woble.c
+ ./util/checkpatch.pl -f btmtk_chip_reset.c
+ ./util/checkpatch.pl -f btmtk_queue.c
+ ./util/checkpatch.pl -f btmtk_char_dev.c
diff --git a/bt_power_table/btPowerTable.dat b/bt_power_table/btPowerTable.dat
new file mode 100644
index 0000000..7f5acfb
--- /dev/null
+++ b/bt_power_table/btPowerTable.dat
@@ -0,0 +1,21 @@
+#Please follow the example pattern
+#There are some SPACES between parameter and parameter
+
+[Country Code]
+[Index] BR_EDR_PWR_MODE, | EDR_MAX_TX_PWR, | BLE_DEFAULT_TX_PWR, | BLE_DEFAULT_TX_PWR_2M, | BLE_LR_S2, | BLE_LR_S8
+
+[AU,SA]
+[BT0] 1, 1.75, 1.5, 1, 1, 1
+[BT1] 1, 2.75, 2.5, 2, 1, 1
+
+[TW,US]
+[BT0] 1, 14, 15, 16, 20, 20
+[BT1] 1, 17, 17, 17, 20, 20
+
+[JP]
+[BT0] 0, 5.25, -3, -3, -2, -2
+[BT1] 0, 5.5, -2.5, -2, -2, -2
+
+[DE]
+[BT0] 0, -32, -29, -29, -29, -29
+[BT1] 0, -32, -29, -29, -29, -29
diff --git a/btmtk_buffer_mode.c b/btmtk_buffer_mode.c
new file mode 100644
index 0000000..23c3717
--- /dev/null
+++ b/btmtk_buffer_mode.c
@@ -0,0 +1,267 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#include "btmtk_buffer_mode.h"
+
+static struct btmtk_buffer_mode_struct btmtk_buffer_mode;
+
+static int btmtk_buffer_mode_check_auto_mode(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ u16 addr = 1;
+ u8 value = 0;
+
+ if (buffer_mode->efuse_mode != AUTO_MODE)
+ return 0;
+
+ if (btmtk_efuse_read(buffer_mode->bdev, addr, &value)) {
+ BTMTK_WARN("read fail");
+ BTMTK_WARN("Use EEPROM Bin file mode");
+ buffer_mode->efuse_mode = BIN_FILE_MODE;
+ return -EIO;
+ }
+
+ if (value == ((buffer_mode->bdev->chip_id & 0xFF00) >> 8)) {
+ BTMTK_WARN("get efuse[1]: 0x%02x", value);
+ BTMTK_WARN("use efuse mode");
+ buffer_mode->efuse_mode = EFUSE_MODE;
+ } else {
+ BTMTK_WARN("get efuse[1]: 0x%02x", value);
+ BTMTK_WARN("Use EEPROM Bin file mode");
+ buffer_mode->efuse_mode = BIN_FILE_MODE;
+ }
+
+ return 0;
+}
+
+static int btmtk_buffer_mode_parse_mode(uint8_t *buf, size_t buf_size)
+{
+ int efuse_mode = EFUSE_MODE;
+ char *p_buf = NULL;
+ char *ptr = NULL, *p = NULL;
+
+ if (!buf) {
+ BTMTK_WARN("buf is null");
+ return efuse_mode;
+ } else if (buf_size < (strlen(BUFFER_MODE_SWITCH_FIELD) + 2)) {
+ BTMTK_WARN("incorrect buf size(%d)", (int)buf_size);
+ return efuse_mode;
+ }
+
+ p_buf = kmalloc(buf_size + 1, GFP_KERNEL);
+ if (!p_buf)
+ return efuse_mode;
+ memcpy(p_buf, buf, buf_size);
+ p_buf[buf_size] = '\0';
+
+ /* find string */
+ p = ptr = strstr(p_buf, BUFFER_MODE_SWITCH_FIELD);
+ if (!ptr) {
+ BTMTK_ERR("Can't find %s", BUFFER_MODE_SWITCH_FIELD);
+ goto out;
+ }
+
+ if (p > p_buf) {
+ p--;
+ while ((*p == ' ') && (p != p_buf))
+ p--;
+ if (*p == '#') {
+ BTMTK_ERR("It's not EEPROM - Bin file mode");
+ goto out;
+ }
+ }
+
+ /* check access mode */
+ ptr += (strlen(BUFFER_MODE_SWITCH_FIELD) + 1);
+ BTMTK_WARN("It's EEPROM bin mode: %c", *ptr);
+ efuse_mode = *ptr - '0';
+ if (efuse_mode > AUTO_MODE)
+ efuse_mode = EFUSE_MODE;
+out:
+ kfree(p_buf);
+ return efuse_mode;
+}
+
+static int btmtk_buffer_mode_set_addr(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ u8 cmd[SET_ADDRESS_CMD_LEN] = {0x01, 0x1A, 0xFC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ u8 event[SET_ADDRESS_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0x1A, 0xFC, 0x00};
+ int ret = 0;
+
+ if (buffer_mode->bt0_mac[0] == 0x00 && buffer_mode->bt0_mac[1] == 0x00
+ && buffer_mode->bt0_mac[2] == 0x00 && buffer_mode->bt0_mac[3] == 0x00
+ && buffer_mode->bt0_mac[4] == 0x00 && buffer_mode->bt0_mac[5] == 0x00) {
+ BTMTK_WARN("BDAddr is Zero, not set");
+ } else {
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET + 5] = buffer_mode->bt0_mac[0];
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET + 4] = buffer_mode->bt0_mac[1];
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET + 3] = buffer_mode->bt0_mac[2];
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET + 2] = buffer_mode->bt0_mac[3];
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET + 1] = buffer_mode->bt0_mac[4];
+ cmd[SET_ADDRESS_CMD_PAYLOAD_OFFSET] = buffer_mode->bt0_mac[5];
+
+ BTMTK_INFO("%s: SEND BDADDR = "MACSTR, __func__, MAC2STR(buffer_mode->bt0_mac));
+ ret = btmtk_main_send_cmd(buffer_mode->bdev,
+ cmd, SET_ADDRESS_CMD_LEN,
+ event, SET_ADDRESS_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ }
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+static int btmtk_buffer_mode_set_radio(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ u8 cmd[SET_RADIO_CMD_LEN] = {0x01, 0x2C, 0xFC, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ u8 event[SET_RADIO_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0x2C, 0xFC, 0x00};
+ int ret = 0;
+
+ cmd[SET_RADIO_CMD_EDR_DEF_OFFSET] = buffer_mode->bt0_radio.radio_0 & 0x3F; /* edr_init_pwr */
+ cmd[SET_RADIO_CMD_BLE_OFFSET] = buffer_mode->bt0_radio.radio_2 & 0x3F; /* ble_default_pwr */
+ cmd[SET_RADIO_CMD_EDR_MAX_OFFSET] = buffer_mode->bt0_radio.radio_1 & 0x3F; /* edr_max_pwr */
+ cmd[SET_RADIO_CMD_EDR_MODE_OFFSET] = (buffer_mode->bt0_radio.radio_0 & 0xC0) >> 6; /* edr_pwr_mode */
+
+ BTMTK_INFO_RAW(cmd, SET_RADIO_CMD_LEN, "%s: Send len[%d]", __func__, SET_RADIO_CMD_LEN);
+ ret = btmtk_main_send_cmd(buffer_mode->bdev,
+ cmd, SET_RADIO_CMD_LEN,
+ event, SET_RADIO_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+static int btmtk_buffer_mode_set_group_boundary(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ u8 cmd[SET_GRP_CMD_LEN] = {0x01, 0xEA, 0xFC, 0x09, 0x02, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ u8 event[SET_GRP_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0xEA, 0xFC, 0x00};
+ int ret = 0;
+
+ memcpy(&cmd[SET_GRP_CMD_PAYLOAD_OFFSET], buffer_mode->bt0_ant0_grp_boundary, BUFFER_MODE_GROUP_LENGTH);
+
+ BTMTK_INFO_RAW(cmd, SET_GRP_CMD_LEN, "%s: Send len[%d]", __func__, SET_GRP_CMD_LEN);
+ ret = btmtk_main_send_cmd(buffer_mode->bdev,
+ cmd, SET_GRP_CMD_LEN,
+ event, SET_GRP_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+static int btmtk_buffer_mode_set_power_offset(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ u8 cmd[SET_PWR_OFFSET_CMD_LEN] = {0x01, 0xEA, 0xFC, 0x0A,
+ 0x02, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ u8 event[SET_PWR_OFFSET_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0xEA, 0xFC, 0x00};
+ int ret = 0;
+
+ memcpy(&cmd[SET_PWR_OFFSET_CMD_PAYLOAD_OFFSET], buffer_mode->bt0_ant0_pwr_offset, BUFFER_MODE_CAL_LENGTH);
+
+ BTMTK_INFO_RAW(cmd, SET_PWR_OFFSET_CMD_LEN, "%s: Send len[%d]", __func__, SET_PWR_OFFSET_CMD_LEN);
+ ret = btmtk_main_send_cmd(buffer_mode->bdev,
+ cmd, SET_PWR_OFFSET_CMD_LEN,
+ event, SET_PWR_OFFSET_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+int btmtk_buffer_mode_send(struct btmtk_buffer_mode_struct *buffer_mode)
+{
+ int ret = 0;
+
+ if (buffer_mode == NULL) {
+ BTMTK_INFO("buffer_mode is NULL, not support");
+ return -EIO;
+ }
+
+ if (btmtk_buffer_mode_check_auto_mode(buffer_mode)) {
+ BTMTK_ERR("check auto mode failed");
+ return -EIO;
+ }
+
+ if (buffer_mode->efuse_mode == BIN_FILE_MODE) {
+ ret = btmtk_buffer_mode_set_addr(buffer_mode);
+ if (ret < 0)
+ BTMTK_ERR("set addr failed");
+
+ ret = btmtk_buffer_mode_set_radio(buffer_mode);
+ if (ret < 0)
+ BTMTK_ERR("set radio failed");
+
+ ret = btmtk_buffer_mode_set_group_boundary(buffer_mode);
+ if (ret < 0)
+ BTMTK_ERR("set group_boundary failed");
+
+ ret = btmtk_buffer_mode_set_power_offset(buffer_mode);
+ if (ret < 0)
+ BTMTK_ERR("set power_offset failed");
+ }
+ return 0;
+}
+
+void btmtk_buffer_mode_initialize(struct btmtk_dev *bdev, struct btmtk_buffer_mode_struct **buffer_mode)
+{
+ int ret = 0;
+ u32 code_len = 0;
+
+ btmtk_buffer_mode.bdev = bdev;
+ ret = btmtk_load_code_from_setting_files(BUFFER_MODE_SWITCH_FILE, bdev->intf_dev, &code_len, bdev);
+
+ btmtk_buffer_mode.efuse_mode = btmtk_buffer_mode_parse_mode(bdev->setting_file, code_len);
+ if (btmtk_buffer_mode.efuse_mode == EFUSE_MODE)
+ return;
+
+ if (bdev->flavor)
+ (void)snprintf(btmtk_buffer_mode.file_name, MAX_BIN_FILE_NAME_LEN, "EEPROM_MT%04x_1a.bin",
+ bdev->chip_id & 0xffff);
+ else
+ (void)snprintf(btmtk_buffer_mode.file_name, MAX_BIN_FILE_NAME_LEN, "EEPROM_MT%04x_1.bin",
+ bdev->chip_id & 0xffff);
+
+ ret = btmtk_load_code_from_setting_files(btmtk_buffer_mode.file_name, bdev->intf_dev, &code_len, bdev);
+ if (ret < 0) {
+ BTMTK_ERR("set load %s failed", btmtk_buffer_mode.file_name);
+ return;
+ }
+
+ memcpy(btmtk_buffer_mode.bt0_mac, &bdev->setting_file[BT0_MAC_OFFSET],
+ BUFFER_MODE_MAC_LENGTH);
+ memcpy(btmtk_buffer_mode.bt1_mac, &bdev->setting_file[BT1_MAC_OFFSET],
+ BUFFER_MODE_MAC_LENGTH);
+ memcpy(&btmtk_buffer_mode.bt0_radio, &bdev->setting_file[BT0_RADIO_OFFSET],
+ BUFFER_MODE_RADIO_LENGTH);
+ memcpy(&btmtk_buffer_mode.bt1_radio, &bdev->setting_file[BT1_RADIO_OFFSET],
+ BUFFER_MODE_RADIO_LENGTH);
+ memcpy(btmtk_buffer_mode.bt0_ant0_grp_boundary, &bdev->setting_file[BT0_GROUP_ANT0_OFFSET],
+ BUFFER_MODE_GROUP_LENGTH);
+ memcpy(btmtk_buffer_mode.bt0_ant1_grp_boundary, &bdev->setting_file[BT0_GROUP_ANT1_OFFSET],
+ BUFFER_MODE_GROUP_LENGTH);
+ memcpy(btmtk_buffer_mode.bt1_ant0_grp_boundary, &bdev->setting_file[BT1_GROUP_ANT0_OFFSET],
+ BUFFER_MODE_GROUP_LENGTH);
+ memcpy(btmtk_buffer_mode.bt1_ant1_grp_boundary, &bdev->setting_file[BT1_GROUP_ANT1_OFFSET],
+ BUFFER_MODE_GROUP_LENGTH);
+ memcpy(btmtk_buffer_mode.bt0_ant0_pwr_offset, &bdev->setting_file[BT0_CAL_ANT0_OFFSET],
+ BUFFER_MODE_CAL_LENGTH);
+ memcpy(btmtk_buffer_mode.bt0_ant1_pwr_offset, &bdev->setting_file[BT0_CAL_ANT1_OFFSET],
+ BUFFER_MODE_CAL_LENGTH);
+ memcpy(btmtk_buffer_mode.bt1_ant0_pwr_offset, &bdev->setting_file[BT1_CAL_ANT0_OFFSET],
+ BUFFER_MODE_CAL_LENGTH);
+ memcpy(btmtk_buffer_mode.bt1_ant1_pwr_offset, &bdev->setting_file[BT1_CAL_ANT1_OFFSET],
+ BUFFER_MODE_CAL_LENGTH);
+
+ *buffer_mode = &btmtk_buffer_mode;
+}
+
diff --git a/btmtk_char_dev.c b/btmtk_char_dev.c
new file mode 100644
index 0000000..53f1c78
--- /dev/null
+++ b/btmtk_char_dev.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+
+#include "btmtk_main.h"
+#include "btmtk_chip_if.h"
+#include "btmtk_fw_log.h"
+#include "btmtk_queue.h"
+#if 0 // Simfex
+#include "btmtk_dbg_tp_evt_if.h"
+#endif
+
+MODULE_LICENSE("Dual BSD/GPL");
+
+#if (USE_DEVICE_NODE == 1)
+#include "btmtk_proj_sp.h"
+
+/*
+ *******************************************************************************
+ * M A C R O S
+ *******************************************************************************
+ */
+#define BT_DRIVER_NAME "mtk_bt_chrdev"
+#define BT_DRIVER_NODE_NAME "stpbt"
+
+/*
+ *******************************************************************************
+ * C O N S T A N T S
+ *******************************************************************************
+ */
+#define BT_BUFFER_SIZE (4096)
+#define FTRACE_STR_LOG_SIZE (256)
+#define COMBO_IOC_MAGIC 0xb0
+#define COMBO_IOCTL_BT_HOST_DEBUG _IOW(COMBO_IOC_MAGIC, 4, void*)
+#define COMBO_IOCTL_BT_INTTRX _IOW(COMBO_IOC_MAGIC, 5, void*)
+#define IOCTL_BT_HOST_DEBUG_BUF_SIZE (32)
+#define IOCTL_BT_HOST_INTTRX_SIZE (128)
+#define TRIGGER_HW_ERR_EVT_COUNT (1000)
+/*
+ *******************************************************************************
+ * D A T A T Y P E S
+ *******************************************************************************
+ */
+enum chip_reset_state {
+ CHIP_RESET_NONE,
+ CHIP_RESET_START,
+ CHIP_RESET_END,
+ CHIP_RESET_NOTIFIED
+};
+
+/*
+ *******************************************************************************
+ * P U B L I C D A T A
+ *******************************************************************************
+ */
+
+/*
+ *******************************************************************************
+ * P R I V A T E D A T A
+ *******************************************************************************
+ */
+static int32_t BT_devs = 1;
+static int32_t BT_major = 192;
+module_param(BT_major, uint, 0444);
+static struct cdev BT_cdev;
+static struct class *BT_class;
+static struct device *BT_dev;
+
+static uint8_t i_buf[BT_BUFFER_SIZE]; /* Input buffer for read */
+static uint8_t o_buf[BT_BUFFER_SIZE]; /* Output buffer for write */
+static uint8_t ioc_buf[IOCTL_BT_HOST_INTTRX_SIZE];
+
+extern struct btmtk_dev *g_sbdev;
+#if 0 // Simfex
+extern struct btmtk_btif_dev g_btif_dev;
+extern void bthost_debug_init(void);
+extern void bthost_debug_save(uint32_t id, uint32_t value, char *desc);
+#endif
+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_t flag;
+static int32_t 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_t rstflag = CHIP_RESET_NONE;
+static uint8_t HCI_EVT_HW_ERROR[] = {0x04, 0x10, 0x01, 0x00};
+static loff_t rd_offset;
+static uint32_t hw_err_retry;
+
+/*
+ *******************************************************************************
+ * F U N C T I O N S
+ *******************************************************************************
+ */
+extern int main_driver_init(void);
+extern void main_driver_exit(void);
+
+static int32_t ftrace_print(const uint8_t *str, ...)
+{
+#ifdef CONFIG_TRACING
+ va_list args;
+ uint8_t temp_string[FTRACE_STR_LOG_SIZE];
+
+ if (bt_ftrace_flag) {
+ va_start(args, str);
+ if (vsnprintf(temp_string, FTRACE_STR_LOG_SIZE, str, args) < 0)
+ BTMTK_INFO("%s: vsnprintf error", __func__);
+ va_end(args);
+ trace_printk("%s\n", temp_string);
+ }
+#endif
+ return 0;
+}
+
+static size_t bt_report_hw_error(uint8_t *buf, size_t count, loff_t *f_pos)
+{
+ size_t bytes_rest = 0, bytes_read = 0;
+
+ BTMTK_DBG("%s start", __func__);
+ if (*f_pos == 0)
+ BTMTK_INFO("Send Hardware Error event to stack to restart Bluetooth");
+
+ 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;
+}
+
+#if 0 // Simfex
+static void bt_state_cb(u_int8_t state)
+{
+ switch (state) {
+
+ case FUNC_ON:
+ rstflag = CHIP_RESET_NONE;
+ break;
+ case RESET_START:
+ rstflag = CHIP_RESET_START;
+ break;
+ case FUNC_OFF:
+ if (rstflag != CHIP_RESET_START) {
+ rstflag = CHIP_RESET_NONE;
+ break;
+ }
+ case RESET_END:
+ rstflag = CHIP_RESET_END;
+ rd_offset = 0;
+ flag = 1;
+ wake_up_interruptible(&inq);
+ wake_up(&BT_wq);
+ break;
+ default:
+ break;
+ }
+}
+#endif
+
+static void BT_event_cb(void)
+{
+ 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__);
+}
+
+static unsigned int BT_poll(struct file *filp, poll_table *wait)
+{
+ uint32_t mask = 0;
+
+ //BTMTK_DBG("%s : rstflag[%d]", __func__, rstflag);
+ //bt_dbg_tp_evt(TP_ACT_POLL, 0, 0, NULL);
+ if ((!btmtk_rx_data_valid() && rstflag == CHIP_RESET_NONE) ||
+ (rstflag == CHIP_RESET_START) || (rstflag == CHIP_RESET_NOTIFIED)) {
+ /*
+ * 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 (!((!btmtk_rx_data_valid() && rstflag == CHIP_RESET_NONE) ||
+ (rstflag == CHIP_RESET_START) || (rstflag == CHIP_RESET_NOTIFIED)))
+ 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(uint8_t *buf, size_t count, uint32_t flags)
+{
+ int32_t retval = 0;
+
+#if 0 // Simfex
+ bt_dbg_tp_evt(TP_ACT_WR_IN, 0, count, buf);
+#endif
+ retval = btmtk_send_data(g_sbdev->hdev, buf, count);
+
+ if (retval < 0)
+ BTMTK_ERR("%s: bt_core_send_data failed, retval %d", __func__, retval);
+ else if (retval == 0) {
+ /*
+ * TX queue cannot be digested in time and no space is available for write.
+ *
+ * If nonblocking mode, return -EAGAIN to let user retry,
+ * native program should not call write with no delay.
+ */
+ if (flags & O_NONBLOCK) {
+ BTMTK_WARN_LIMITTED("%s: Non-blocking write, no space is available!", __func__);
+ retval = -EAGAIN;
+ } else {
+ /* TODO: blocking write case */
+ }
+ } else
+ BTMTK_DBG("%s: Write bytes %d/%zd", __func__, retval, count);
+
+ return retval;
+}
+
+static ssize_t BT_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ ssize_t retval = 0;
+ size_t count = iov_iter_count(from);
+
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&wr_mtx);
+
+ if (rstflag != CHIP_RESET_NONE) {
+ BTMTK_ERR("whole chip reset occurs! rstflag=%d", rstflag);
+ retval = -EIO;
+ goto OUT;
+ }
+
+ if (count > 0) {
+ if (count > BT_BUFFER_SIZE) {
+ BTMTK_WARN("Shorten write count from %zd to %d", count, BT_BUFFER_SIZE);
+ count = BT_BUFFER_SIZE;
+ }
+
+ if (copy_from_iter(o_buf, count, from) != count) {
+ retval = -EFAULT;
+ goto OUT;
+ }
+
+ retval = __bt_write(o_buf, count, iocb->ki_filp->f_flags);
+ }
+
+OUT:
+ up(&wr_mtx);
+ return retval;
+}
+
+static ssize_t BT_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ ssize_t retval = 0;
+
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&wr_mtx);
+
+ BTMTK_DBG("count %zd pos %lld", count, *f_pos);
+
+ if (rstflag != CHIP_RESET_NONE) {
+ BTMTK_ERR("whole chip reset occurs! rstflag=%d", rstflag);
+ retval = -EIO;
+ goto OUT;
+ }
+
+ if (count > 0) {
+ if (count > BT_BUFFER_SIZE) {
+ BTMTK_WARN("Shorten write count from %zd to %d", count, BT_BUFFER_SIZE);
+ count = BT_BUFFER_SIZE;
+ }
+
+ if (copy_from_user(o_buf, buf, count)) {
+ retval = -EFAULT;
+ goto OUT;
+ }
+
+ retval = __bt_write(o_buf, count, filp->f_flags);
+ }
+
+OUT:
+ up(&wr_mtx);
+ return retval;
+}
+
+static ssize_t BT_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ ssize_t retval = 0;
+
+#if 0 // Simfex
+ bt_dbg_tp_evt(TP_ACT_RD_IN, 0, count, NULL);
+#endif
+ ftrace_print("%s get called, count %zd", __func__, count);
+ down(&rd_mtx);
+
+ if (rstflag != CHIP_RESET_NONE) {
+ BTMTK_DBG("%s: rstflag != CHIP_RESET_NONE", __func__);
+ while (rstflag != CHIP_RESET_END && rstflag != CHIP_RESET_NONE) {
+ /*
+ * If nonblocking mode, return -EIO directly.
+ * O_NONBLOCK is specified during open().
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ BTMTK_ERR_LIMITTED("Non-blocking read, whole chip reset occurs! rstflag=%d", 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 = CHIP_RESET_NOTIFIED;
+ }
+
+ if (copy_to_user(buf, i_buf, retval)) {
+ retval = -EFAULT;
+ if (rstflag == CHIP_RESET_NOTIFIED)
+ rstflag = CHIP_RESET_END;
+ }
+
+ goto OUT;
+ }
+
+ if (count > BT_BUFFER_SIZE) {
+ BTMTK_WARN("Shorten read count from %zd to %d", count, BT_BUFFER_SIZE);
+ count = BT_BUFFER_SIZE;
+ }
+
+ do {
+ retval = btmtk_receive_data(g_sbdev->hdev, i_buf, count);
+ if (retval < 0) {
+ BTMTK_ERR("bt_core_receive_data failed, retval %d", retval);
+ goto OUT;
+ } else if (retval == 0) { /* Got nothing, wait for RX queue's signal */
+ /*
+ * If nonblocking mode, return -EAGAIN to let user retry.
+ * O_NONBLOCK is specified during open().
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ BTMTK_ERR_LIMITTED("Non-blocking read, no data is available!");
+ retval = -EAGAIN;
+ if (hw_err_retry++ > TRIGGER_HW_ERR_EVT_COUNT) {
+ BTMTK_ERR("%s: hw_err_retry[%d] > %d", __func__, hw_err_retry
+ , TRIGGER_HW_ERR_EVT_COUNT);
+ retval = bt_report_hw_error(i_buf, count, &rd_offset);
+ if (rd_offset == sizeof(HCI_EVT_HW_ERROR))
+ rd_offset = 0;
+
+ if (copy_to_user(buf, i_buf, retval))
+ retval = -EFAULT;
+ }
+ goto OUT;
+ }
+
+ wait_event(BT_wq, flag != 0);
+ flag = 0;
+ } else { /* Got something from RX queue */
+#if 0 // Simfex
+ bt_dbg_tp_evt(TP_ACT_RD_OUT, 0, retval, i_buf);
+#endif
+ break;
+ }
+ } while (btmtk_rx_data_valid() && rstflag == CHIP_RESET_NONE);
+
+ if (retval == 0) {
+ if (rstflag != CHIP_RESET_END) { /* Should never happen */
+ WARN(1, "Blocking read is waken up in unexpected case, rstflag=%d", 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 = CHIP_RESET_NOTIFIED;
+ }
+ }
+ }
+
+ if (copy_to_user(buf, i_buf, retval)) {
+ hw_err_retry = 0;
+ retval = -EFAULT;
+ if (rstflag == CHIP_RESET_NOTIFIED)
+ rstflag = CHIP_RESET_END;
+ }
+
+OUT:
+ up(&rd_mtx);
+ return retval;
+}
+
+int _ioctl_copy_evt_to_buf(uint8_t *buf, int len)
+{
+ BTMTK_INFO("%s", __func__);
+ memset(ioc_buf, 0x00, sizeof(ioc_buf));
+ ioc_buf[0] = 0x04; // evt packet type
+ memcpy(ioc_buf + 1, buf, len); // copy evt to ioctl buffer
+ BTMTK_INFO_RAW(ioc_buf, len + 1, "%s: len[%d] RX: ", __func__, len + 1);
+ return 0;
+}
+
+static long BT_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int32_t retval = 0;
+
+ BTMTK_INFO("%s: cmd[0x%08x]", __func__, cmd);
+ memset(ioc_buf, 0x00, sizeof(ioc_buf));
+ switch (cmd) {
+ case COMBO_IOCTL_BT_HOST_DEBUG:
+ /*
+ * input: arg(buf_size = 32): id[0:3], value[4:7], desc[8:31]
+ * output: none
+ */
+ if (copy_from_user(ioc_buf, (uint8_t __user *)arg, IOCTL_BT_HOST_DEBUG_BUF_SIZE))
+ retval = -EFAULT;
+ else {
+ uint32_t *pint32 = (uint32_t *)&ioc_buf[0];
+
+ BTMTK_INFO("%s: id[%x], value[0x%08x], desc[%s]", __func__, pint32[0], pint32[1], &ioc_buf[8]);
+#if 0
+ bthost_debug_save(pint32[0], pint32[1], (char *)&ioc_buf[8]);
+#endif
+ }
+ break;
+ case COMBO_IOCTL_BT_INTTRX:
+ /*
+ * input: arg(buf_size = 128): hci cmd raw data
+ * output: arg(buf_size = 128): hci evt raw data
+ */
+ if (copy_from_user(ioc_buf, (uint8_t __user *)arg, IOCTL_BT_HOST_INTTRX_SIZE))
+ retval = -EFAULT;
+ else {
+ BTMTK_INFO_RAW(ioc_buf, ioc_buf[3] + 4, "%s: len[%d] TX: ", __func__, ioc_buf[3] + 4);
+ /* DynamicAdjustTxPower function */
+ if (ioc_buf[0] == 0x01 && ioc_buf[1] == 0x2D && ioc_buf[2] == 0xFC) {
+ if (ioc_buf[4] == HCI_CMD_DY_ADJ_PWR_QUERY)
+ btmtk_query_tx_power(g_sbdev, _ioctl_copy_evt_to_buf);
+ else
+ btmtk_set_tx_power(g_sbdev, ioc_buf[5], _ioctl_copy_evt_to_buf);
+ if (copy_to_user((uint8_t __user *)arg, ioc_buf, IOCTL_BT_HOST_INTTRX_SIZE))
+ retval = -EFAULT;
+ } else
+ retval = -EFAULT;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static 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)
+{
+ int32_t ret;
+
+ __pm_stay_awake(bt_wakelock);
+ BTMTK_INFO("major %d minor %d (pid %d)", imajor(inode), iminor(inode), current->pid);
+
+ /* Turn on BT */
+ if (g_sbdev == NULL) {
+ BTMTK_ERR("g_sbdev == NULL");
+ return -1;
+ }
+
+ if (g_sbdev->hdev == NULL) {
+ BTMTK_ERR("g_sbdev->hdev == NULL");
+ return -1;
+ }
+
+ if (g_sbdev->hdev->open == NULL) {
+ BTMTK_ERR("g_sbdev->hdev->open == NULL");
+ return -1;
+ }
+
+ ret = g_sbdev->hdev->open(g_sbdev->hdev);
+ if (ret) {
+ BTMTK_ERR("BT turn on fail!");
+ __pm_relax(bt_wakelock);
+ return ret;
+ }
+
+ btmtk_register_rx_event_cb(BT_event_cb);
+
+ bt_ftrace_flag = 1;
+ hw_err_retry = 0;
+ __pm_relax(bt_wakelock);
+#if 0 // Simfex
+ bthost_debug_init();
+#endif
+ BTMTK_INFO("BT turn on OK!");
+ return 0;
+}
+
+static int BT_close(struct inode *inode, struct file *file)
+{
+ int32_t ret;
+
+ __pm_stay_awake(bt_wakelock);
+ BTMTK_INFO("%s: major %d minor %d (pid %d)", __func__, imajor(inode), iminor(inode), current->pid);
+ bt_ftrace_flag = 0;
+ //bt_core_unregister_rx_event_cb();
+
+ /* err handle for uart disconnect */
+ if (g_sbdev == NULL) {
+ BTMTK_ERR("%s: g_sbdev == NULL", __func__);
+ return -1;
+ }
+
+ if (g_sbdev->hdev == NULL) {
+ BTMTK_ERR("%s: g_sbdev->hdev == NULL", __func__);
+ return -1;
+ }
+
+ if (g_sbdev->hdev->close == NULL) {
+ BTMTK_ERR("%s: g_sbdev->hdev->close == NULL", __func__);
+ return -1;
+ }
+
+ ret = g_sbdev->hdev->close(g_sbdev->hdev);
+ __pm_relax(bt_wakelock);
+#if 0 // Simfex
+ bthost_debug_init();
+#endif
+
+ if (ret) {
+ BTMTK_ERR("BT turn off fail!");
+ return ret;
+ }
+
+ BTMTK_INFO("BT turn off OK!");
+ 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
+};
+
+int BT_init(void)
+{
+ int32_t alloc_err = 0;
+ int32_t cdv_err = 0;
+ dev_t dev = MKDEV(BT_major, 0);
+
+ sema_init(&wr_mtx, 1);
+ sema_init(&rd_mtx, 1);
+ init_waitqueue_head(&inq);
+
+ /* Initialize wake lock for I/O operation */
+ bt_wakelock = wakeup_source_register(NULL, "bt_drv_io");
+
+#if 0
+ g_btif_dev.state_change_cb[0] = fw_log_bt_state_cb;
+ g_btif_dev.state_change_cb[1] = bt_state_cb;
+#endif
+
+ /* Allocate char device */
+ alloc_err = register_chrdev_region(dev, BT_devs, BT_DRIVER_NAME);
+ if (alloc_err) {
+ BTMTK_ERR("Failed to register device numbers");
+ goto alloc_error;
+ }
+
+ cdev_init(&BT_cdev, &BT_fops);
+ BT_cdev.owner = THIS_MODULE;
+
+ cdv_err = cdev_add(&BT_cdev, dev, BT_devs);
+ if (cdv_err)
+ goto cdv_error;
+
+ BT_class = class_create(THIS_MODULE, BT_DRIVER_NODE_NAME);
+ if (IS_ERR(BT_class))
+ goto create_node_error;
+ BT_dev = device_create(BT_class, NULL, dev, NULL, BT_DRIVER_NODE_NAME);
+ if (IS_ERR(BT_dev))
+ goto create_node_error;
+
+ BTMTK_INFO("%s driver(major %d) installed", BT_DRIVER_NAME, BT_major);
+ return 0;
+
+create_node_error:
+ if (BT_class && !IS_ERR(BT_class)) {
+ class_destroy(BT_class);
+ BT_class = NULL;
+ }
+
+ cdev_del(&BT_cdev);
+
+cdv_error:
+ unregister_chrdev_region(dev, BT_devs);
+
+alloc_error:
+ wakeup_source_unregister(bt_wakelock);
+ return -1;
+}
+
+void BT_exit(void)
+{
+ dev_t dev = MKDEV(BT_major, 0);
+
+ if (BT_dev && !IS_ERR(BT_dev)) {
+ device_destroy(BT_class, dev);
+ BT_dev = NULL;
+ }
+ if (BT_class && !IS_ERR(BT_class)) {
+ class_destroy(BT_class);
+ BT_class = NULL;
+ }
+
+ cdev_del(&BT_cdev);
+ unregister_chrdev_region(dev, BT_devs);
+
+#if 0 // Simfex
+ g_btif_dev.state_change_cb[0] = NULL;
+ g_btif_dev.state_change_cb[1] = NULL;
+#endif
+
+ /* Destroy wake lock */
+ wakeup_source_unregister(bt_wakelock);
+
+ BTMTK_INFO("%s driver removed", BT_DRIVER_NAME);
+}
+
+inline int btmtk_chardev_init(void)
+{
+ return BT_init();
+}
+
+#else
+
+int BT_init(void)
+{
+ BTMTK_INFO("%s: not device node, return", __func__);
+ return 0;
+}
+
+void BT_exit(void)
+{
+ BTMTK_INFO("%s: not device node, return", __func__);
+}
+
+#endif // USE_DEVICE_NODE
+
+
+#ifdef MTK_WCN_REMOVE_KERNEL_MODULE
+/* build-in mode */
+int mtk_wcn_stpbt_drv_init(void)
+{
+ return main_driver_init();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_init);
+
+void mtk_wcn_stpbt_drv_exit(void)
+{
+ return main_driver_exit();
+}
+EXPORT_SYMBOL(mtk_wcn_stpbt_drv_exit);
+#endif
diff --git a/btmtk_chip_reset.c b/btmtk_chip_reset.c
new file mode 100644
index 0000000..161cdb9
--- /dev/null
+++ b/btmtk_chip_reset.c
@@ -0,0 +1,246 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#include "btmtk_chip_reset.h"
+
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+static void btmtk_reset_timer(unsigned long arg)
+{
+ struct btmtk_dev *bdev = (struct btmtk_dev *)arg;
+
+ BTMTK_INFO("%s: chip_reset not trigger in %d seconds, trigger it directly",
+ __func__, CHIP_RESET_TIMEOUT);
+ schedule_work(&bdev->reset_waker);
+}
+#else
+static void btmtk_reset_timer(struct timer_list *timer)
+{
+ struct btmtk_dev *bdev = from_timer(bdev, timer, chip_reset_timer);
+
+ BTMTK_INFO("%s: chip_reset not trigger in %d seconds, trigger it directly",
+ __func__, CHIP_RESET_TIMEOUT);
+ schedule_work(&bdev->reset_waker);
+}
+#endif
+
+void btmtk_reset_timer_add(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s: create chip_reset timer", __func__);
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+ init_timer(&bdev->chip_reset_timer);
+ bdev->chip_reset_timer.function = btmtk_reset_timer;
+ bdev->chip_reset_timer.data = (unsigned long)bdev;
+#else
+ timer_setup(&bdev->chip_reset_timer, btmtk_reset_timer, 0);
+#endif
+}
+
+void btmtk_reset_timer_update(struct btmtk_dev *bdev)
+{
+ mod_timer(&bdev->chip_reset_timer, jiffies + CHIP_RESET_TIMEOUT * HZ);
+}
+
+void btmtk_reset_timer_del(struct btmtk_dev *bdev)
+{
+ if (timer_pending(&bdev->chip_reset_timer)) {
+ del_timer_sync(&bdev->chip_reset_timer);
+ BTMTK_INFO("%s exit", __func__);
+ }
+}
+
+void btmtk_reset_waker(struct work_struct *work)
+{
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, reset_waker);
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ int state = BTMTK_STATE_INIT;
+ int cif_event = 0, err = 0;
+ int cur = 0;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+
+ /* Check chip state is ok to do reset or not */
+ state = btmtk_get_chip_state(bdev);
+ if (state == BTMTK_STATE_SUSPEND) {
+ BTMTK_INFO("%s suspend state don't do chip reset!", __func__);
+ return;
+ }
+
+ if (state == BTMTK_STATE_PROBE) {
+ bmain_info->chip_reset_flag = 1;
+ BTMTK_INFO("%s just do whole chip reset in probe stage!", __func__);
+ }
+
+ if (state == BTMTK_STATE_CLOSED) {
+ BTMTK_WARN("%s: chip is closed(%d), not trigger reset", __func__, state);
+ return;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate == BTMTK_FOPS_STATE_CLOSED) {
+ BTMTK_WARN("%s: fops is closed(%d), not trigger reset", __func__, fstate);
+ return;
+ }
+
+ btmtk_reset_timer_del(bdev);
+ if (atomic_read(&bmain_info->chip_reset) ||
+ atomic_read(&bmain_info->subsys_reset)) {
+ BTMTK_INFO("%s return, chip_reset = %d, subsys_reset = %d!", __func__,
+ atomic_read(&bmain_info->chip_reset), atomic_read(&bmain_info->subsys_reset));
+ return;
+ }
+
+#if (USE_DEVICE_NODE == 0)
+ if (bmain_info->hif_hook.dump_debug_sop)
+ bmain_info->hif_hook.dump_debug_sop(bdev);
+#endif
+
+ DUMP_TIME_STAMP("chip_reset_start");
+ cif_event = HIF_EVENT_SUBSYS_RESET;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return;
+ }
+
+#if (USE_DEVICE_NODE == 0)
+ if (!bdev->bt_cfg.support_dongle_reset) {
+ BTMTK_ERR("%s chip_reset is not support", __func__);
+ return;
+ }
+#else
+ /* for only reset but no coredump */
+ reinit_completion(&bdev->dump_comp);
+#endif
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if (USE_DEVICE_NODE == 1)
+ /* put after set chip state to avoid bt close without wait dump cr */
+ if (bmain_info->hif_hook.dump_debug_sop)
+ bmain_info->hif_hook.dump_debug_sop(bdev);
+#endif
+
+ BTMTK_INFO("%s: Receive a byte (0xFF)", __func__);
+ /* read interrupt EP15 CR */
+
+ bdev->sco_num = 0;
+
+ if (bmain_info->chip_reset_flag == 0 &&
+ atomic_read(&bmain_info->subsys_reset_conti_count) < BTMTK_MAX_SUBSYS_RESET_COUNT) {
+ if (bmain_info->hif_hook.subsys_reset) {
+ cur = atomic_cmpxchg(&bmain_info->subsys_reset, BTMTK_RESET_DONE, BTMTK_RESET_DOING);
+ if (cur == BTMTK_RESET_DOING) {
+ BTMTK_INFO("%s: subsys reset in progress, return", __func__);
+ return;
+ }
+ DUMP_TIME_STAMP("subsys_chip_reset_start");
+ /*
+ * Discard this part for SP platform since Consys power is off after BT off,
+ * Nothing is remain on memory after BT off, so leave do this at BT on
+ * for SP platform
+ */
+ err = bmain_info->hif_hook.subsys_reset(bdev);
+ atomic_set(&bmain_info->subsys_reset, BTMTK_RESET_DONE);
+ if (err < 0) {
+ BTMTK_INFO("subsys reset failed, do whole chip reset!");
+ goto L0RESET;
+ }
+ atomic_inc(&bmain_info->subsys_reset_count);
+ atomic_inc(&bmain_info->subsys_reset_conti_count);
+ DUMP_TIME_STAMP("subsys_chip_reset_end");
+
+ bmain_info->reset_stack_flag = HW_ERR_CODE_CHIP_RESET;
+
+ err = btmtk_cap_init(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk init failed!");
+ goto L0RESET;
+ }
+#if (USE_DEVICE_NODE == 0)
+
+ err = btmtk_load_rom_patch(bdev);
+ if (err < 0) {
+ BTMTK_INFO("btmtk load rom patch failed!");
+ goto L0RESET;
+ }
+#endif
+ btmtk_send_hw_err_to_host(bdev);
+ btmtk_woble_wake_unlock(bdev);
+ if (bmain_info->hif_hook.chip_reset_notify)
+ bmain_info->hif_hook.chip_reset_notify(bdev);
+ } else {
+ err = -1;
+ BTMTK_INFO("%s: Not support subsys chip reset", __func__);
+ goto L0RESET;
+ }
+ } else {
+ err = -1;
+ BTMTK_INFO("%s: chip_reset_flag is %d, subsys_reset_count %d",
+ __func__,
+ bmain_info->chip_reset_flag,
+ atomic_read(&bmain_info->subsys_reset_conti_count));
+ }
+
+L0RESET:
+ if (err < 0) {
+ /* L0.5 reset failed or not support, do whole chip reset */
+ /* TODO: need to confirm with usb host when suspend fail, to do chip reset,
+ * because usb3.0 need to toggle reset pin after hub_event unfreeze,
+ * otherwise, it will not occur disconnect on Capy Platform. When Mstar
+ * chip has usb3.0 port, we will use Mstar platform to do comparison
+ * test, then found the final solution.
+ */
+ /* msleep(2000); */
+ if (bmain_info->hif_hook.whole_reset) {
+ DUMP_TIME_STAMP("whole_chip_reset_start");
+ bmain_info->hif_hook.whole_reset(bdev);
+ atomic_inc(&bmain_info->whole_reset_count);
+ DUMP_TIME_STAMP("whole_chip_reset_end");
+ } else {
+ BTMTK_INFO("%s: Not support whole chip reset, reset reset_conti_count to 0", __func__);
+ atomic_set(&bmain_info->subsys_reset_conti_count, 0);
+#if (USE_DEVICE_NODE == 1)
+ btmtk_send_hw_err_to_host(bdev);
+#endif
+ }
+ }
+
+ DUMP_TIME_STAMP("chip_reset_end");
+ /* Set End/Error state */
+ if (err < 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+
+#if (USE_DEVICE_NODE == 1)
+ complete(&bdev->dump_comp);
+#endif
+}
+
+void btmtk_reset_trigger(struct btmtk_dev *bdev)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (atomic_read(&bmain_info->chip_reset) ||
+ atomic_read(&bmain_info->subsys_reset)) {
+ BTMTK_INFO("%s return, chip_reset = %d, subsys_reset = %d!", __func__,
+ atomic_read(&bmain_info->chip_reset), atomic_read(&bmain_info->subsys_reset));
+ return;
+ }
+
+ schedule_work(&bdev->reset_waker);
+}
+
diff --git a/btmtk_fw_log.c b/btmtk_fw_log.c
new file mode 100644
index 0000000..7ab4410
--- /dev/null
+++ b/btmtk_fw_log.c
@@ -0,0 +1,1198 @@
+/* Define for proce node */
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include "btmtk_fw_log.h"
+
+#if (USE_DEVICE_NODE == 1)
+#include "connv3_debug_utility.h"
+#include "connv3_mcu_log.h"
+#include "connv3.h"
+#endif
+
+/*
+ * BT Logger Tool will turn on/off Firmware Picus log, and set 3 log levels (Low, SQC and Debug)
+ * For extention capability, driver does not check the value range.
+ *
+ * Combine log state and log level to below settings:
+ * - 0x00: OFF
+ * - 0x01: Low Power
+ * - 0x02: SQC
+ * - 0x03: Debug
+ */
+#define BT_FWLOG_DEFAULT_LEVEL 0x02
+#define CONNV3_XML_SIZE 1024 /* according to connv3_dump_test.c */
+
+/* CTD BT log function and log status */
+static wait_queue_head_t BT_log_wq;
+static struct semaphore ioctl_mtx;
+static uint8_t g_bt_on = BT_FWLOG_OFF;
+static uint8_t g_log_on = BT_FWLOG_OFF;
+static uint8_t g_log_level = BT_FWLOG_DEFAULT_LEVEL;
+static uint8_t g_log_current = BT_FWLOG_OFF;
+/* For fwlog dev node setting */
+static struct btmtk_fops_fwlog *g_fwlog;
+
+const struct file_operations BT_fopsfwlog = {
+ .open = btmtk_fops_openfwlog,
+ .release = btmtk_fops_closefwlog,
+ .read = btmtk_fops_readfwlog,
+ .write = btmtk_fops_writefwlog,
+ .poll = btmtk_fops_pollfwlog,
+ .unlocked_ioctl = btmtk_fops_unlocked_ioctlfwlog,
+ .compat_ioctl = btmtk_fops_compat_ioctlfwlog
+};
+
+/** read_write for proc */
+static int btmtk_proc_show(struct seq_file *m, void *v);
+static int btmtk_proc_open(struct inode *inode, struct file *file);
+static int btmtk_proc_chip_reset_count_open(struct inode *inode, struct file *file);
+static int btmtk_proc_chip_reset_count_show(struct seq_file *m, void *v);
+
+#if (KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE)
+static const struct file_operations BT_proc_fops = {
+ .open = btmtk_proc_open,
+ .read = seq_read,
+ .release = single_release,
+};
+
+static const struct file_operations BT_proc_chip_reset_count_fops = {
+ .open = btmtk_proc_chip_reset_count_open,
+ .read = seq_read,
+ .release = single_release,
+};
+#else
+static const struct proc_ops BT_proc_fops = {
+ .proc_open = btmtk_proc_open,
+ .proc_read = seq_read,
+ .proc_release = single_release,
+};
+
+static const struct proc_ops BT_proc_chip_reset_count_fops = {
+ .proc_open = btmtk_proc_chip_reset_count_open,
+ .proc_read = seq_read,
+ .proc_release = single_release,
+};
+#endif
+__weak int32_t btmtk_intcmd_wmt_utc_sync(void)
+{
+ BTMTK_ERR("weak function %s not implement", __func__);
+ return -1;
+}
+
+__weak int32_t btmtk_intcmd_set_fw_log(uint8_t flag)
+{
+ BTMTK_ERR("weak function %s not implement", __func__);
+ return -1;
+}
+
+void fw_log_bt_state_cb(uint8_t state)
+{
+ uint8_t on_off;
+ /* sp use BTMTK_FOPS_STATE_OPENED to judge state */
+ on_off = (state == FUNC_ON) ? BT_FWLOG_ON : BT_FWLOG_OFF;
+ BTMTK_INFO("bt_on(0x%x) state(%d) on_off(0x%x)", g_bt_on, state, on_off);
+
+ if (g_bt_on != on_off) {
+ // changed
+ if (on_off == BT_FWLOG_OFF) { // should turn off
+ g_bt_on = BT_FWLOG_OFF;
+ BTMTK_INFO("BT func off, no need to send hci cmd");
+ } else {
+ g_bt_on = BT_FWLOG_ON;
+ if (g_log_current) {
+ btmtk_intcmd_set_fw_log(g_log_current);
+ btmtk_intcmd_wmt_utc_sync();
+ }
+ }
+ }
+}
+
+void fw_log_bt_event_cb(void)
+{
+ wake_up_interruptible(&BT_log_wq);
+}
+
+static int btmtk_proc_show(struct seq_file *m, void *v)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ (void)seq_printf(m, "patch version:%s driver version:%s\n", bmain_info->fw_version_str, VERSION);
+ return 0;
+}
+
+static int btmtk_proc_chip_reset_count_show(struct seq_file *m, void *v)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ (void)seq_printf(m, "whole_reset_count=%d subsys_reset_count=%d\n",
+ atomic_read(&bmain_info->whole_reset_count),
+ atomic_read(&bmain_info->subsys_reset_count));
+ return 0;
+}
+
+static int btmtk_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, btmtk_proc_show, NULL);
+}
+
+static int btmtk_proc_chip_reset_count_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, btmtk_proc_chip_reset_count_show, NULL);
+}
+
+static void btmtk_proc_create_new_entry(void)
+{
+ struct proc_dir_entry *proc_show_entry = NULL;
+ struct proc_dir_entry *proc_show_chip_reset_count_entry = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s, proc initialized", __func__);
+
+ bmain_info->proc_dir = proc_mkdir("stpbt", NULL);
+ if (bmain_info->proc_dir == NULL) {
+ BTMTK_ERR("Unable to creat dir");
+ return;
+ }
+ proc_show_entry = proc_create("bt_fw_version", 0640, bmain_info->proc_dir, &BT_proc_fops);
+ if (proc_show_entry == NULL) {
+ BTMTK_ERR("Unable to creat bt_fw_version node");
+ remove_proc_entry("stpbt", NULL);
+ }
+
+ proc_show_chip_reset_count_entry = proc_create(PROC_BT_CHIP_RESET_COUNT, 0640,
+ bmain_info->proc_dir, &BT_proc_chip_reset_count_fops);
+ if (proc_show_chip_reset_count_entry == NULL) {
+ BTMTK_ERR("Unable to creat %s node", PROC_BT_CHIP_RESET_COUNT);
+ remove_proc_entry(PROC_ROOT_DIR, NULL);
+ }
+
+}
+
+static void btmtk_proc_delete_entry(void)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (bmain_info->proc_dir == NULL)
+ return;
+
+ remove_proc_entry("bt_fw_version", bmain_info->proc_dir);
+ BTMTK_INFO("%s, proc device node and folder removed!!", __func__);
+ remove_proc_entry(PROC_BT_CHIP_RESET_COUNT, bmain_info->proc_dir);
+ BTMTK_INFO("%s, proc device node and folder %s removed!!", __func__, PROC_BT_CHIP_RESET_COUNT);
+
+ remove_proc_entry(PROC_ROOT_DIR, NULL);
+ bmain_info->proc_dir = NULL;
+}
+
+static int btmtk_fops_initfwlog(void)
+{
+#ifdef STATIC_REGISTER_FWLOG_NODE
+ static int BT_majorfwlog = FIXED_STPBT_MAJOR_DEV_ID + 1;
+ dev_t devIDfwlog = MKDEV(BT_majorfwlog, 1);
+#else
+ static int BT_majorfwlog;
+ dev_t devIDfwlog = MKDEV(BT_majorfwlog, 0);
+#endif
+ int ret = 0;
+ int cdevErr = 0;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: Start %s", __func__, BT_FWLOG_DEV_NODE);
+
+ if (g_fwlog == NULL) {
+ g_fwlog = kzalloc(sizeof(*g_fwlog), GFP_KERNEL);
+ if (!g_fwlog) {
+ BTMTK_ERR("%s: alloc memory fail (g_data)", __func__);
+ return -1;
+ }
+ }
+
+#ifdef STATIC_REGISTER_FWLOG_NODE
+ ret = register_chrdev_region(devIDfwlog, 1, "BT_chrdevfwlog");
+ if (ret) {
+ BTMTK_ERR("%s: fail to register chrdev(%x)", __func__, devIDfwlog);
+ goto alloc_error;
+ }
+#else
+ ret = alloc_chrdev_region(&devIDfwlog, 0, 1, "BT_chrdevfwlog");
+ if (ret) {
+ BTMTK_ERR("%s: fail to allocate chrdev", __func__);
+ goto alloc_error;
+ }
+#endif
+ BT_majorfwlog = MAJOR(devIDfwlog);
+
+ cdev_init(&g_fwlog->BT_cdevfwlog, &BT_fopsfwlog);
+ g_fwlog->BT_cdevfwlog.owner = THIS_MODULE;
+
+ cdevErr = cdev_add(&g_fwlog->BT_cdevfwlog, devIDfwlog, 1);
+ if (cdevErr)
+ goto cdv_error;
+
+ g_fwlog->pBTClass = class_create(THIS_MODULE, BT_FWLOG_DEV_NODE);
+ if (IS_ERR(g_fwlog->pBTClass)) {
+ BTMTK_ERR("%s: class create fail, error code(%ld)\n", __func__, PTR_ERR(g_fwlog->pBTClass));
+ goto create_node_error;
+ }
+
+ g_fwlog->pBTDevfwlog = device_create(g_fwlog->pBTClass, NULL, devIDfwlog, NULL,
+ "%s", BT_FWLOG_DEV_NODE);
+ if (IS_ERR(g_fwlog->pBTDevfwlog)) {
+ BTMTK_ERR("%s: device(stpbtfwlog) create fail, error code(%ld)", __func__,
+ PTR_ERR(g_fwlog->pBTDevfwlog));
+ goto create_node_error;
+ }
+ BTMTK_INFO("%s: BT_majorfwlog %d, devIDfwlog %d", __func__, BT_majorfwlog, devIDfwlog);
+
+ g_fwlog->g_devIDfwlog = devIDfwlog;
+ sema_init(&ioctl_mtx, 1);
+
+ //if (is_mt66xx(g_sbdev->chip_id)) {
+ if (bmain_info->hif_hook.log_init) {
+ bmain_info->hif_hook.log_init(fw_log_bt_event_cb);
+ //bmain_info->hif_hook.log_register_cb(fw_log_bt_event_cb);
+ init_waitqueue_head(&BT_log_wq);
+ } else {
+ spin_lock_init(&g_fwlog->fwlog_lock);
+ skb_queue_head_init(&g_fwlog->fwlog_queue);
+ skb_queue_head_init(&g_fwlog->usr_opcode_queue);//opcode
+ init_waitqueue_head(&(g_fwlog->fw_log_inq));
+ }
+
+ atomic_set(&bmain_info->fwlog_ref_cnt, 0);
+ BTMTK_INFO("%s: End", __func__);
+ return 0;
+
+create_node_error:
+ if (g_fwlog->pBTClass) {
+ class_destroy(g_fwlog->pBTClass);
+ g_fwlog->pBTClass = NULL;
+ }
+
+cdv_error:
+ if (cdevErr == 0)
+ cdev_del(&g_fwlog->BT_cdevfwlog);
+
+ if (ret == 0)
+ unregister_chrdev_region(devIDfwlog, 1);
+
+alloc_error:
+ kfree(g_fwlog);
+ g_fwlog = NULL;
+
+ return -1;
+}
+
+static int btmtk_fops_exitfwlog(void)
+{
+ dev_t devIDfwlog = g_fwlog->g_devIDfwlog;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: Start\n", __func__);
+
+ //if (is_mt66xx(g_sbdev->chip_id))
+ if (bmain_info->hif_hook.log_deinit)
+ bmain_info->hif_hook.log_deinit();
+
+ if (g_fwlog->pBTDevfwlog) {
+ device_destroy(g_fwlog->pBTClass, devIDfwlog);
+ g_fwlog->pBTDevfwlog = NULL;
+ }
+
+ if (g_fwlog->pBTClass) {
+ class_destroy(g_fwlog->pBTClass);
+ g_fwlog->pBTClass = NULL;
+ }
+ BTMTK_INFO("%s: pBTDevfwlog, pBTClass done\n", __func__);
+
+ cdev_del(&g_fwlog->BT_cdevfwlog);
+ unregister_chrdev_region(devIDfwlog, 1);
+ BTMTK_INFO("%s: BT_chrdevfwlog driver removed.\n", __func__);
+
+ kfree(g_fwlog);
+
+ return 0;
+}
+static int flag;
+void btmtk_init_node(void)
+{
+ if (flag == 1)
+ return;
+
+ flag = 1;
+ btmtk_proc_create_new_entry();
+ if (btmtk_fops_initfwlog() < 0)
+ BTMTK_ERR("%s: create stpbtfwlog failed.\n", __func__);
+}
+
+void btmtk_deinit_node(void)
+{
+ if (flag != 1)
+ return;
+
+ flag = 0;
+ btmtk_proc_delete_entry();
+ if (btmtk_fops_exitfwlog() < 0)
+ BTMTK_ERR("%s: release stpbtfwlog failed.\n", __func__);
+}
+
+ssize_t btmtk_fops_readfwlog(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ int copyLen = 0;
+ ulong flags = 0;
+ struct sk_buff *skb = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ static unsigned int fwlog_count;
+
+ //if (is_mt66xx(g_sbdev->chip_id)) {
+ if (bmain_info->hif_hook.log_read_to_user) {
+ copyLen = bmain_info->hif_hook.log_read_to_user(buf, count);
+ BTMTK_DBG_LIMITTED("%s: fw log counter[%d]", __func__, fwlog_count++);
+ return copyLen;
+ }
+
+ /* picus read a queue, it may occur performace issue */
+ spin_lock_irqsave(&g_fwlog->fwlog_lock, flags);
+ if (skb_queue_len(&g_fwlog->fwlog_queue))
+ skb = skb_dequeue(&g_fwlog->fwlog_queue);
+
+ spin_unlock_irqrestore(&g_fwlog->fwlog_lock, flags);
+ if (skb == NULL)
+ return 0;
+
+ if (skb->len <= count) {
+ if (copy_to_user(buf, skb->data, skb->len))
+ BTMTK_ERR("%s: copy_to_user failed!", __func__);
+ copyLen = skb->len;
+ } else {
+ BTMTK_DBG("%s: socket buffer length error(count: %d, skb.len: %d)",
+ __func__, (int)count, skb->len);
+ }
+ kfree_skb(skb);
+
+ return copyLen;
+}
+ssize_t btmtk_fops_writefwlog(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ int i = 0, len = 0, ret = -1;
+ int hci_idx = 0;
+ int vlen = 0, index = 3;
+ struct sk_buff *skb = NULL;
+#if (USE_DEVICE_NODE == 0)
+ struct sk_buff *skb_opcode = NULL;
+#endif
+ int state = BTMTK_STATE_INIT;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ u8 *i_fwlog_buf = NULL;
+ u8 *o_fwlog_buf = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ struct btmtk_dev **pp_bdev = btmtk_get_pp_bdev();
+
+ /* only 7xxx will use writefwlog, 66xx not used */
+ /*if (is_mt66xx(bdev->chip_id)) {
+ * BTMTK_WARN("%s: not implement!", __func__);
+ * return 0;
+ * }
+ */
+
+ i_fwlog_buf = kmalloc(HCI_MAX_COMMAND_BUF_SIZE, GFP_KERNEL);
+ if (!i_fwlog_buf) {
+ BTMTK_ERR("%s: alloc i_fwlog_buf failed", __func__);
+ return -ENOMEM;
+ }
+
+ o_fwlog_buf = kmalloc(HCI_MAX_COMMAND_SIZE, GFP_KERNEL);
+ if (!o_fwlog_buf) {
+ BTMTK_ERR("%s: alloc o_fwlog_buf failed", __func__);
+ ret = -ENOMEM;
+ kfree(i_fwlog_buf);
+ return -ENOMEM;
+ }
+
+ if (count > HCI_MAX_COMMAND_BUF_SIZE) {
+ BTMTK_ERR("%s: your command is larger than maximum length, count = %zd",
+ __func__, count);
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ memset(i_fwlog_buf, 0, HCI_MAX_COMMAND_BUF_SIZE);
+ memset(o_fwlog_buf, 0, HCI_MAX_COMMAND_SIZE);
+
+ if (buf == NULL || count == 0) {
+ BTMTK_ERR("%s: worng input data", __func__);
+ ret = -ENODATA;
+ goto exit;
+ }
+
+ if (copy_from_user(i_fwlog_buf, buf, count) != 0) {
+ BTMTK_ERR("%s: Failed to copy data", __func__);
+ ret = -ENODATA;
+ goto exit;
+ }
+
+ /* For log level, EX: echo log_lvl=1 > /dev/stpbtfwlog */
+ if (strncmp(i_fwlog_buf, "log_lvl=", strlen("log_lvl=")) == 0) {
+ u8 val = *(i_fwlog_buf + strlen("log_lvl=")) - '0';
+
+ if (val > BTMTK_LOG_LVL_MAX || val <= 0) {
+ BTMTK_ERR("Got incorrect value for log level(%d)", val);
+ ret = -EINVAL;
+ goto exit;
+ }
+ btmtk_log_lvl = val;
+ BTMTK_INFO("btmtk_log_lvl = %d", btmtk_log_lvl);
+ ret = count;
+ goto exit;
+ }
+
+ /* For bperf, EX: echo bperf=1 > /dev/stpbtfwlog */
+ if (strncmp(i_fwlog_buf, "bperf=", strlen("bperf=")) == 0) {
+ u8 val = *(i_fwlog_buf + strlen("bperf=")) - '0';
+
+ g_fwlog->btmtk_bluetooth_kpi = val;
+ BTMTK_INFO("%s: set bluetooth KPI feature(bperf) to %d", __func__, g_fwlog->btmtk_bluetooth_kpi);
+ ret = count;
+ goto exit;
+ }
+
+ if (strncmp(i_fwlog_buf, "set_para=", strlen("set_para=")) == 0) {
+ u8 val = *(i_fwlog_buf + strlen("set_para=")) - '0';
+
+ if (bmain_info->hif_hook.set_para)
+ bmain_info->hif_hook.set_para(pp_bdev[hci_idx], val);
+ else
+ BTMTK_WARN("%s: not support set_para", __func__);
+ ret = count;
+ goto exit;
+ }
+
+ if (strncmp(i_fwlog_buf, "chip_reset=", strlen("chip_reset=")) == 0) {
+ u8 val = *(i_fwlog_buf + strlen("chip_reset=")) - '0';
+
+ bmain_info->chip_reset_flag = val;
+ BTMTK_INFO("%s: set chip reset flag to %d", __func__, bmain_info->chip_reset_flag);
+ ret = count;
+ goto exit;
+ }
+ if (strncmp(i_fwlog_buf, "whole chip reset", strlen("whole chip reset")) == 0) {
+ BTMTK_INFO("whole chip reset start");
+ bmain_info->chip_reset_flag = 1;
+ btmtk_reset_trigger(pp_bdev[hci_idx]);
+ ret = count;
+ goto exit;
+ }
+ if (strncmp(i_fwlog_buf, "subsys chip reset", strlen("subsys chip reset")) == 0) {
+ BTMTK_INFO("subsys chip reset");
+ bmain_info->chip_reset_flag = 0;
+ btmtk_reset_trigger(pp_bdev[hci_idx]);
+ ret = count;
+ goto exit;
+ }
+ if (strncmp(i_fwlog_buf, "dump chip reset", strlen("dump chip reset")) == 0) {
+ BTMTK_INFO("subsys chip reset = %d", atomic_read(&bmain_info->subsys_reset_count));
+ BTMTK_INFO("whole chip reset = %d", atomic_read(&bmain_info->whole_reset_count));
+ ret = count;
+ goto exit;
+ }
+
+ if (strncmp(i_fwlog_buf, "dump btsnoop", strlen("dump btsnoop")) == 0) {
+ btmtk_hci_snoop_print_to_log();
+ ret = count;
+ goto exit;
+ }
+
+#ifdef BTMTK_DEBUG_SOP
+ if (strncmp(i_fwlog_buf, "dump test", strlen("dump test")) == 0) {
+ btmtk_load_debug_sop_register(pp_bdev[hci_idx]->debug_sop_file_name,
+ pp_bdev[hci_idx]->intf_dev, pp_bdev[hci_idx]);
+ ret = count;
+ goto exit;
+ }
+
+ if (strncmp(i_fwlog_buf, "dump clean", strlen("dump clean")) == 0) {
+ btmtk_clean_debug_reg_file(pp_bdev[hci_idx]);
+ ret = count;
+ goto exit;
+ }
+#endif
+
+ if (strncmp(i_fwlog_buf, "dump_debug=", strlen("dump_debug")) == 0) {
+ u8 val = *(i_fwlog_buf + strlen("dump_debug=")) - '0';
+
+ if (bmain_info->hif_hook.dump_debug_sop) {
+ BTMTK_INFO("%s: dump_debug(%s)", __func__,
+ (val == 0) ? "SLEEP" :
+ ((val == 1) ? "WAKEUP" :
+ ((val == 2) ? "NO_RESPONSE" : "ERROR")));
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ ret = bmain_info->hif_hook.open(pp_bdev[hci_idx]->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_open failed", __func__);
+ ret = count;
+ goto exit;
+ }
+ }
+ bmain_info->hif_hook.dump_debug_sop(pp_bdev[hci_idx]);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ ret = bmain_info->hif_hook.close(pp_bdev[hci_idx]->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_close failed", __func__);
+ ret = count;
+ goto exit;
+ }
+ }
+ } else {
+ BTMTK_INFO("%s: not support", __func__);
+ }
+ ret = count;
+ goto exit;
+ }
+
+ /* hci input command format : echo 01 be fc 01 05 > /dev/stpbtfwlog */
+ /* We take the data from index three to end. */
+ for (i = 0; i < count; i++) {
+ char *pos = i_fwlog_buf + i;
+ char temp_str[3] = {'\0'};
+ long res = 0;
+
+ if (*pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n') {
+ continue;
+ } else if (*pos == '0' && (*(pos + 1) == 'x' || *(pos + 1) == 'X')) {
+ i++;
+ continue;
+ } else if (!(*pos >= '0' && *pos <= '9') && !(*pos >= 'A' && *pos <= 'F')
+ && !(*pos >= 'a' && *pos <= 'f')) {
+ BTMTK_ERR("%s: There is an invalid input(%c)", __func__, *pos);
+ ret = -EINVAL;
+ goto exit;
+ }
+ temp_str[0] = *pos;
+ temp_str[1] = *(pos + 1);
+ i++;
+ ret = kstrtol(temp_str, 16, &res);
+ if (ret == 0)
+ o_fwlog_buf[len++] = (u8)res;
+ else
+ BTMTK_ERR("%s: Convert %s failed(%d)", __func__, temp_str, ret);
+ }
+
+ if (o_fwlog_buf[0] != HCI_COMMAND_PKT && o_fwlog_buf[0] != FWLOG_TYPE) {
+ BTMTK_ERR("%s: Not support 0x%02X yet", __func__, o_fwlog_buf[0]);
+ ret = -EPROTONOSUPPORT;
+ goto exit;
+ }
+ /* check HCI command length */
+ if (len > HCI_MAX_COMMAND_SIZE) {
+ BTMTK_ERR("%s: command is larger than max buf size, length = %d", __func__, len);
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ skb = alloc_skb(count + BT_SKB_RESERVE, GFP_ATOMIC);
+ if (!skb) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ /* send HCI command */
+ bt_cb(skb)->pkt_type = HCI_COMMAND_PKT;
+
+ /* format */
+ /* 0xF0 XX XX 00 01 AA 10 BB CC CC CC CC ... */
+ /* XX XX total length */
+ /* 00 : hci index setting type */
+ /* AA hci index to indicate which hci send following command*/
+ /* 10 : raw data type*/
+ /* BB command length */
+ /* CC command */
+ if (o_fwlog_buf[0] == FWLOG_TYPE) {
+ while (index < ((o_fwlog_buf[2] << 8) + o_fwlog_buf[1])) {
+ switch (o_fwlog_buf[index]) {
+ case FWLOG_HCI_IDX: /* hci index */
+ vlen = o_fwlog_buf[index + 1];
+ hci_idx = o_fwlog_buf[index + 2];
+ BTMTK_DBG("%s: send to hci%d", __func__, hci_idx);
+ index += (FWLOG_ATTR_TL_SIZE + vlen);
+ break;
+ case FWLOG_TX: /* tx raw data */
+ vlen = o_fwlog_buf[index + 1];
+ memcpy(skb->data, o_fwlog_buf + index + FWLOG_ATTR_TL_SIZE, vlen);
+ skb->len = vlen;
+ index = index + FWLOG_ATTR_TL_SIZE + vlen;
+ break;
+ default:
+ BTMTK_WARN("%s: Invalid opcode", __func__);
+ ret = -1;
+ goto free_skb;
+ }
+ }
+ } else {
+ memcpy(skb->data, o_fwlog_buf, len);
+ skb->len = len;
+#if defined(DRV_RETURN_SPECIFIC_HCE_ONLY) && (DRV_RETURN_SPECIFIC_HCE_ONLY == 1) && (USE_DEVICE_NODE == 0)
+ // 0xFC26 is get link & profile information command.
+ if (*(uint16_t *)(o_fwlog_buf + 1) != 0xFC26) {
+ skb_opcode = alloc_skb(len + FWLOG_PRSV_LEN, GFP_ATOMIC);
+ if (!skb_opcode) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ ret = -ENOMEM;
+ goto exit;
+ }
+ memcpy(skb_opcode->data, (o_fwlog_buf + 1), 2);
+ BTMTK_INFO("opcode is %02x,%02x", skb_opcode->data[0], skb_opcode->data[1]);
+ skb_queue_tail(&g_fwlog->usr_opcode_queue, skb_opcode);
+ }
+#endif
+ }
+
+ /* won't send command if g_bdev not define */
+ if (pp_bdev[hci_idx]->hdev == NULL) {
+ BTMTK_DBG("%s: pp_bdev[%d] not define", __func__, hci_idx);
+ ret = count;
+ goto free_skb;
+ }
+
+ state = btmtk_get_chip_state(pp_bdev[hci_idx]);
+ if (state != BTMTK_STATE_WORKING) {
+ BTMTK_WARN("%s: current is in suspend/resume/standby/dump/disconnect (%d).",
+ __func__, state);
+ ret = -EBADFD;
+ goto free_skb;
+ }
+
+ fstate = btmtk_fops_get_state(pp_bdev[hci_idx]);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open yet(%d)!", __func__, fstate);
+ ret = -ENODEV;
+ goto free_skb;
+ }
+
+ if (pp_bdev[hci_idx]->power_state == BTMTK_DONGLE_STATE_POWER_OFF) {
+ BTMTK_WARN("%s: dongle state already power off, do not write", __func__);
+ ret = -EFAULT;
+ goto free_skb;
+ }
+
+#if (USE_DEVICE_NODE == 0)
+ /* clean fwlog queue before enable picus log */
+ if (skb_queue_len(&g_fwlog->fwlog_queue) && skb->data[0] == 0x01
+ && skb->data[1] == 0x5d && skb->data[2] == 0xfc && skb->data[4] == 0x00) {
+ skb_queue_purge(&g_fwlog->fwlog_queue);
+ BTMTK_INFO("clean fwlog_queue, skb_queue_len = %d", skb_queue_len(&g_fwlog->fwlog_queue));
+ }
+
+ btmtk_dispatch_fwlog_bluetooth_kpi(pp_bdev[hci_idx], skb->data, skb->len, KPI_WITHOUT_TYPE);
+#endif
+ ret = bmain_info->hif_hook.send_cmd(pp_bdev[hci_idx], skb, 0, 0, (int)BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ goto free_skb;
+ } else
+ BTMTK_INFO("%s: OK", __func__);
+
+ BTMTK_INFO("%s: Write end(len: %d)", __func__, len);
+ ret = count;
+ goto exit;
+
+free_skb:
+ kfree_skb(skb);
+ skb = NULL;
+#if (USE_DEVICE_NODE == 0)
+ /* clean opcode queue if bt is disable */
+ skb_queue_purge(&g_fwlog->usr_opcode_queue);
+#endif
+exit:
+ kfree(i_fwlog_buf);
+ kfree(o_fwlog_buf);
+
+ return ret; /* If input is correct should return the same length */
+}
+
+int btmtk_fops_openfwlog(struct inode *inode, struct file *file)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ atomic_inc(&bmain_info->fwlog_ref_cnt);
+ BTMTK_INFO("%s: Start.", __func__);
+
+ return 0;
+}
+
+int btmtk_fops_closefwlog(struct inode *inode, struct file *file)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ atomic_dec(&bmain_info->fwlog_ref_cnt);
+ BTMTK_INFO("%s: Start.", __func__);
+
+ return 0;
+}
+
+long btmtk_fops_unlocked_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long retval = 0;
+ uint8_t log_tmp = BT_FWLOG_OFF;
+
+ /* only 66xx will use ioctlfwlog, 76xx not used */
+ /* if (!is_mt66xx(g_sbdev->chip_id)) {
+ * BTMTK_WARN("%s: not implement!", __func__);
+ * return 0;
+ *}
+ */
+
+ down(&ioctl_mtx);
+ switch (cmd) {
+ case BT_FWLOG_IOC_ON_OFF:
+ /* Connsyslogger daemon dynamically enable/disable Picus log */
+ BTMTK_INFO("[ON_OFF]arg(%lu) bt_on(0x%x) log_on(0x%x) level(0x%x) log_cur(0x%x)",
+ arg, g_bt_on, g_log_on, g_log_level, g_log_current);
+
+ log_tmp = (arg == 0) ? BT_FWLOG_OFF : BT_FWLOG_ON;
+ if (log_tmp != g_log_on) { // changed
+ g_log_on = log_tmp;
+ g_log_current = g_log_on & g_log_level;
+ if (g_bt_on) {
+ retval = btmtk_intcmd_set_fw_log(g_log_current);
+ btmtk_intcmd_wmt_utc_sync();
+ }
+ }
+ break;
+ case BT_FWLOG_IOC_SET_LEVEL:
+ /* Connsyslogger daemon dynamically set Picus log level */
+ BTMTK_INFO("[SET_LEVEL]arg(%lu) bt_on(0x%x) log_on(0x%x) level(0x%x) log_cur(0x%x)",
+ arg, g_bt_on, g_log_on, g_log_level, g_log_current);
+
+ log_tmp = (uint8_t)arg;
+ if (log_tmp != g_log_level) {
+ 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 = btmtk_intcmd_set_fw_log(g_log_current);
+ btmtk_intcmd_wmt_utc_sync();
+ }
+ }
+ break;
+ case BT_FWLOG_IOC_GET_LEVEL:
+ retval = g_log_level;
+ BTMTK_INFO("[GET_LEVEL]return %ld", retval);
+ break;
+ default:
+ BTMTK_ERR("Unknown cmd: 0x%08x", cmd);
+ retval = -EOPNOTSUPP;
+ break;
+ }
+
+ up(&ioctl_mtx);
+ return retval;
+}
+
+long btmtk_fops_compat_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ BTMTK_DBG("%s: Start.", __func__);
+ return btmtk_fops_unlocked_ioctlfwlog(filp, cmd, arg);
+}
+
+unsigned int btmtk_fops_pollfwlog(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ //if (is_mt66xx(g_sbdev->chip_id)) {
+ if (bmain_info->hif_hook.log_get_buf_size) {
+ poll_wait(file, &BT_log_wq, wait);
+ if (bmain_info->hif_hook.log_get_buf_size() > 0)
+ mask = (POLLIN | POLLRDNORM);
+ } else {
+ poll_wait(file, &g_fwlog->fw_log_inq, wait);
+ if (skb_queue_len(&g_fwlog->fwlog_queue) > 0)
+ mask |= POLLIN | POLLRDNORM; /* readable */
+ }
+ return mask;
+}
+
+static void btmtk_fwdump_wake_lock(void)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: enter", __func__);
+ __pm_stay_awake(bmain_info->fwdump_ws);
+ BTMTK_INFO("%s: exit", __func__);
+}
+
+void btmtk_fwdump_wake_unlock(void)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: enter", __func__);
+ __pm_relax(bmain_info->fwdump_ws);
+ BTMTK_INFO("%s: exit", __func__);
+}
+
+static int btmtk_skb_enq_fwlog(struct btmtk_dev *bdev, void *src, u32 len, u8 type, struct sk_buff_head *queue)
+{
+ struct sk_buff *skb_tmp = NULL;
+ ulong flags = 0;
+ int retry = 10, index = FWLOG_TL_SIZE;
+
+ do {
+ skb_tmp = alloc_skb(len + FWLOG_PRSV_LEN, GFP_ATOMIC);
+ if (skb_tmp != NULL)
+ break;
+ else if (retry <= 0) {
+ pr_err("%s: alloc_skb return 0, error", __func__);
+ return -ENOMEM;
+ }
+ pr_err("%s: alloc_skb return 0, error, retry = %d", __func__, retry);
+ } while (retry-- > 0);
+
+ if (type) {
+ skb_tmp->data[0] = FWLOG_TYPE;
+ /* 01 for dongle index */
+ skb_tmp->data[index] = FWLOG_DONGLE_IDX;
+ skb_tmp->data[index + 1] = sizeof(bdev->dongle_index);
+ skb_tmp->data[index + 2] = bdev->dongle_index;
+ index += (FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN);
+ /* 11 for rx data*/
+ skb_tmp->data[index] = FWLOG_RX;
+ if (type == HCI_ACLDATA_PKT || type == HCI_EVENT_PKT || type == HCI_COMMAND_PKT) {
+ skb_tmp->data[index + 1] = len & 0x00FF;
+ skb_tmp->data[index + 2] = (len & 0xFF00) >> 8;
+ skb_tmp->data[index + 3] = type;
+ index += (HCI_TYPE_SIZE + FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN);
+ } else {
+ skb_tmp->data[index + 1] = len & 0x00FF;
+ skb_tmp->data[index + 2] = (len & 0xFF00) >> 8;
+ index += (FWLOG_ATTR_RX_LEN_LEN + FWLOG_ATTR_TYPE_LEN);
+ }
+ memcpy(&skb_tmp->data[index], src, len);
+ skb_tmp->data[1] = (len + index - FWLOG_TL_SIZE) & 0x00FF;
+ skb_tmp->data[2] = ((len + index - FWLOG_TL_SIZE) & 0xFF00) >> 8;
+ skb_tmp->len = len + index;
+ } else {
+ memcpy(skb_tmp->data, src, len);
+ skb_tmp->len = len;
+ }
+
+
+ spin_lock_irqsave(&g_fwlog->fwlog_lock, flags);
+ skb_queue_tail(queue, skb_tmp);
+ spin_unlock_irqrestore(&g_fwlog->fwlog_lock, flags);
+ return 0;
+}
+
+int btmtk_dispatch_fwlog_bluetooth_kpi(struct btmtk_dev *bdev, u8 *buf, int len, u8 type)
+{
+ static u8 fwlog_blocking_warn;
+ int ret = 0;
+
+ if (g_fwlog->btmtk_bluetooth_kpi &&
+ skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_BLUETOOTH_KPI_QUEUE_COUNT) {
+ /* sent event to queue, picus tool will log it for bluetooth KPI feature */
+ if (btmtk_skb_enq_fwlog(bdev, buf, len, type, &g_fwlog->fwlog_queue) == 0) {
+ wake_up_interruptible(&g_fwlog->fw_log_inq);
+ fwlog_blocking_warn = 0;
+ }
+ } else {
+ if (fwlog_blocking_warn == 0) {
+ fwlog_blocking_warn = 1;
+ pr_warn("btmtk_usb fwlog queue size is full(bluetooth_kpi)");
+ }
+ }
+ return ret;
+}
+
+/* if modify the common part, please sync to another btmtk_dispatch_fwlog */
+#if (USE_DEVICE_NODE == 0)
+int btmtk_dispatch_fwlog(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ static u8 fwlog_picus_blocking_warn;
+ static u8 fwlog_fwdump_blocking_warn;
+ int state = BTMTK_STATE_INIT;
+ u8 hci_reset_event[HCI_RESET_EVT_LEN] = { 0x04, 0x0E, 0x04, 0x01, 0x03, 0x0c, 0x00 };
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ struct sk_buff *skb_opcode = NULL;
+
+ if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) &&
+ skb->data[0] == 0x6f &&
+ skb->data[1] == 0xfc) {
+ static int dump_data_counter;
+ static int dump_data_length;
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_FW_DUMP) {
+ BTMTK_INFO("%s: FW dump begin", __func__);
+ DUMP_TIME_STAMP("FW_dump_start");
+ btmtk_hci_snoop_print_to_log();
+ /* Print too much log, it may cause kernel panic. */
+ dump_data_counter = 0;
+ dump_data_length = 0;
+ btmtk_set_chip_state(bdev, BTMTK_STATE_FW_DUMP);
+ btmtk_fwdump_wake_lock();
+ }
+
+ dump_data_counter++;
+ dump_data_length += skb->len;
+
+ /* coredump */
+ /* print dump data to console */
+ if (dump_data_counter % 1000 == 0) {
+ BTMTK_INFO("%s: FW dump on-going, total_packet = %d, total_length = %d",
+ __func__, dump_data_counter, dump_data_length);
+ }
+
+ /* print dump data to console */
+ if (dump_data_counter < 20)
+ BTMTK_INFO("%s: FW dump data (%d): %s",
+ __func__, dump_data_counter, &skb->data[4]);
+
+ /* In the new generation, we will check the keyword of coredump (; coredump end)
+ * Such as : 79xx
+ */
+ if (skb->data[skb->len - 4] == 'e' &&
+ skb->data[skb->len - 3] == 'n' &&
+ skb->data[skb->len - 2] == 'd') {
+ /* This is the latest coredump packet. */
+ BTMTK_INFO("%s: FW dump end, dump_data_counter = %d", __func__, dump_data_counter);
+ /* TODO: Chip reset*/
+ bmain_info->reset_stack_flag = HW_ERR_CODE_CORE_DUMP;
+ btmtk_fwdump_wake_unlock();
+ DUMP_TIME_STAMP("FW_dump_end");
+ if (bmain_info->hif_hook.waker_notify)
+ bmain_info->hif_hook.waker_notify(bdev);
+ }
+
+ if (skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_ASSERT_QUEUE_COUNT) {
+ /* sent picus data to queue, picus tool will log it */
+ if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len, 0, &g_fwlog->fwlog_queue) == 0) {
+ wake_up_interruptible(&g_fwlog->fw_log_inq);
+ fwlog_fwdump_blocking_warn = 0;
+ }
+ } else {
+ if (fwlog_fwdump_blocking_warn == 0) {
+ fwlog_fwdump_blocking_warn = 1;
+ pr_warn("btmtk fwlog queue size is full(coredump)");
+ }
+ }
+
+ /* change coredump's ACL handle to FF F0 */
+ skb->data[0] = 0xFF;
+ skb->data[1] = 0xF0;
+ } else if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) &&
+ (skb->data[0] == 0xff || skb->data[0] == 0xfe) &&
+ skb->data[1] == 0x05 &&
+ !bdev->bt_cfg.support_picus_to_host) {
+ /* picus or syslog */
+ if (skb_queue_len(&g_fwlog->fwlog_queue) < FWLOG_QUEUE_COUNT) {
+ if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len,
+ FWLOG_TYPE, &g_fwlog->fwlog_queue) == 0) {
+ wake_up_interruptible(&g_fwlog->fw_log_inq);
+ fwlog_picus_blocking_warn = 0;
+ }
+ } else {
+ if (fwlog_picus_blocking_warn == 0) {
+ fwlog_picus_blocking_warn = 1;
+ pr_warn("btmtk fwlog queue size is full(picus)");
+ }
+ }
+ return 1;
+ } else if (memcmp(skb->data, &hci_reset_event[1], HCI_RESET_EVT_LEN - 1) == 0) {
+ BTMTK_INFO("%s: Get RESET_EVENT", __func__);
+ bdev->get_hci_reset = 1;
+ atomic_set(&bmain_info->subsys_reset_conti_count, 0);
+ }
+
+
+ /* filter event from usr cmd */
+ if ((bt_cb(skb)->pkt_type == HCI_EVENT_PKT) &&
+ skb->data[0] == 0x0E) {
+ if (skb_queue_len(&g_fwlog->usr_opcode_queue)) {
+ BTMTK_INFO("%s: opcode queue len is %d", __func__,
+ skb_queue_len(&g_fwlog->usr_opcode_queue));
+ skb_opcode = skb_dequeue(&g_fwlog->usr_opcode_queue);
+ }
+
+ if (skb_opcode == NULL)
+ return 0;
+
+ if (skb_opcode->data[0] == skb->data[3] &&
+ skb_opcode->data[1] == skb->data[4]) {
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: Discard event from user hci command - ", __func__);
+#if defined(DRV_RETURN_SPECIFIC_HCE_ONLY) && (DRV_RETURN_SPECIFIC_HCE_ONLY == 0)
+ // should return to upper layer tool
+ if (btmtk_skb_enq_fwlog(bdev, skb->data, skb->len, FWLOG_TYPE,
+ &g_fwlog->fwlog_queue) == 0) {
+ wake_up_interruptible(&g_fwlog->fw_log_inq);
+ }
+ kfree_skb(skb_opcode);
+#endif
+ return 1;
+ }
+
+ BTMTK_INFO("%s: check opcode fail!", __func__);
+ skb_queue_head(&g_fwlog->usr_opcode_queue, skb_opcode);
+ }
+
+ return 0;
+}
+
+#else // #if (USE_DEVICE_NODE == 0)
+
+/* if modify the common part, please sync to another btmtk_dispatch_fwlog*/
+int btmtk_dispatch_fwlog(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ char xml_log[CONNV3_XML_SIZE] = {0};
+ int state = BTMTK_STATE_INIT;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ struct connv3_issue_info issue_info;
+ int ret = 0, line = 0;
+
+ if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) &&
+ skb->data[0] == 0x6f &&
+ skb->data[1] == 0xfc) {
+ static int dump_data_counter;
+ static int dump_data_length;
+ //static int ori_log_lvl;
+
+ /* remove acl header 6F FC LL LL */
+ skb_pull(skb, 4);
+
+ state = btmtk_get_chip_state(bdev);
+ /* coredump info*/
+ if (state != BTMTK_STATE_FW_DUMP) {
+ BTMTK_INFO("%s: msg: %s len[%d]", __func__, skb->data, skb->len);
+ /* drop "Disable Cache" */
+ if (skb->len > 6 &&
+ skb->data[skb->len - 6] == 'C' &&
+ skb->data[skb->len - 5] == 'a' &&
+ skb->data[skb->len - 4] == 'c' &&
+ skb->data[skb->len - 3] == 'h' &&
+ skb->data[skb->len - 2] == 'e') {
+ BTMTK_INFO("%s: drop Cache", __func__, skb->data, skb->len);
+ return 1;
+ }
+
+ /* drop "bt radio off" */
+ if (skb->len > 10 &&
+ skb->data[skb->len - 10] == 'r' &&
+ skb->data[skb->len - 9] == 'a' &&
+ skb->data[skb->len - 8] == 'd' &&
+ skb->data[skb->len - 7] == 'i' &&
+ skb->data[skb->len - 6] == 'o' &&
+ skb->data[skb->len - 5] == ' ' &&
+ skb->data[skb->len - 4] == 'o' &&
+ skb->data[skb->len - 3] == 'f' &&
+ skb->data[skb->len - 2] == 'f') {
+ BTMTK_INFO("%s: drop radio off", __func__, skb->data, skb->len);
+ return 1;
+ }
+
+ //ori_log_lvl = btmtk_log_lvl;
+ //btmtk_log_lvl = BTMTK_LOG_LVL_INFO;
+ //BTMTK_INFO("%s: FW dump begin, change log level [%d]->[%d]", __func__, ori_log_lvl, btmtk_log_lvl);
+ DUMP_TIME_STAMP("FW_dump_start");
+ /* Print too much log, it may cause kernel panic. */
+ dump_data_counter = 0;
+ dump_data_length = 0;
+ if (bmain_info->hif_hook.coredump_handler == NULL) {
+ BTMTK_ERR("%s: coredump_handler is NULL", __func__);
+ goto coredump_fail;
+ }
+ btmtk_set_chip_state(bdev, BTMTK_STATE_FW_DUMP);
+ reinit_completion(&bdev->dump_comp);
+ btmtk_fwdump_wake_lock();
+ line = __LINE__;
+ ret = connv3_coredump_start(
+ bmain_info->hif_hook.coredump_handler, CONNV3_DRV_TYPE_BT,
+ "BT exception test", skb->data, bmain_info->fw_version_str);
+ if (ret == CONNV3_COREDUMP_ERR_WRONG_STATUS) {
+ BTMTK_ERR("%s: BT previous not end", __func__);
+ connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT previous not end");
+ ret = connv3_coredump_start(
+ bmain_info->hif_hook.coredump_handler, CONNV3_DRV_TYPE_BT,
+ "BT exception test", skb->data, bmain_info->fw_version_str);
+ }
+ if (ret)
+ goto coredump_fail_unlock;
+ line = __LINE__;
+ ret = connv3_coredump_get_issue_info(bmain_info->hif_hook.coredump_handler,
+ &issue_info, xml_log, CONNV3_XML_SIZE);
+ if (ret)
+ goto coredump_fail_unlock;
+ BTMTK_INFO("%s: xml_log: %s, assert_info: %s ", __func__, xml_log, issue_info.assert_info);
+ line = __LINE__;
+ ret = connv3_coredump_send(bmain_info->hif_hook.coredump_handler,
+ "INFO", xml_log, strlen(xml_log));
+ if (ret)
+ goto coredump_fail_unlock;
+ }
+
+ dump_data_counter++;
+ dump_data_length += skb->len;
+
+ /* coredump content*/
+ /* print dump data to console */
+ if (dump_data_counter % 1000 == 0) {
+ BTMTK_INFO("%s: FW dump on-going, total_packet = %d, total_length = %d",
+ __func__, dump_data_counter, dump_data_length);
+ }
+
+ /* print dump data to console */
+ if (dump_data_counter < 20)
+ BTMTK_INFO("%s: FW dump data (%d): %s",
+ __func__, dump_data_counter, skb->data);
+ line = __LINE__;
+ ret = connv3_coredump_send(bmain_info->hif_hook.coredump_handler, "[M]", skb->data, skb->len);
+ if (ret)
+ goto coredump_fail_unlock;
+
+ /* In the new generation, we will check the keyword of coredump (; coredump end)
+ * Such as : 79xx
+ */
+ if (skb->len > 6 &&
+ skb->data[skb->len - 6] == 'p' &&
+ skb->data[skb->len - 5] == ' ' &&
+ skb->data[skb->len - 4] == 'e' &&
+ skb->data[skb->len - 3] == 'n' &&
+ skb->data[skb->len - 2] == 'd') {
+ /* TODO: Chip reset*/
+ bmain_info->reset_stack_flag = HW_ERR_CODE_CORE_DUMP;
+ btmtk_fwdump_wake_unlock();
+ DUMP_TIME_STAMP("FW_dump_end");
+ line = __LINE__;
+ /* This is the latest coredump packet. */
+ BTMTK_INFO("%s: FW dump end, dump_data_counter[%d], dump_data_length[%d]",
+ __func__, dump_data_counter, dump_data_length);
+ //btmtk_log_lvl = ori_log_lvl;
+ ret = connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT assert");
+ if (bmain_info->hif_hook.waker_notify)
+ bmain_info->hif_hook.waker_notify(bdev);
+ BTMTK_DBG("%s: connv3_coredump_end", __func__);
+ if (ret)
+ goto coredump_fail;
+
+ }
+ return 1;
+coredump_fail_unlock:
+ btmtk_fwdump_wake_unlock();
+coredump_fail:
+ BTMTK_ERR("%s: coredump fail ret[%d] line[%d]", __func__, ret, line);
+ connv3_coredump_end(bmain_info->hif_hook.coredump_handler, "BT coredump fail");
+ return 1;
+ } else if ((bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) &&
+ (skb->data[0] == 0xff || skb->data[0] == 0xfe) &&
+ skb->data[1] == 0x05 && bmain_info->hif_hook.log_handler) {
+ /* picus or syslog */
+ /* remove acl header (FF 05 LL LL)*/
+ skb_pull(skb, 4);
+ bmain_info->hif_hook.log_handler(skb->data, skb->len);
+ return 1;
+ }
+
+ return 0;
+}
+#endif // #else (USE_DEVICE_NODE == 1)
+
diff --git a/btmtk_main.c b/btmtk_main.c
new file mode 100644
index 0000000..fe335b6
--- /dev/null
+++ b/btmtk_main.c
@@ -0,0 +1,5172 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/input.h>
+#include <linux/pm_wakeup.h>
+#include <linux/reboot.h>
+#include <linux/string.h>
+
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_fw_log.h"
+#include "btmtk_chip_if.h"
+
+#if (USE_DEVICE_NODE == 1)
+#include "connv3_debug_utility.h"
+#include "btmtk_queue.h"
+#include "connv3.h"
+#include "btmtk_proj_sp.h"
+#endif
+
+#define MTKBT_UNSLEEPABLE_LOCK(x, y) spin_lock_irqsave(x, y)
+#define MTKBT_UNSLEEPABLE_UNLOCK(x, y) spin_unlock_irqsave(x, y)
+
+/* TODO, need to modify the state mutex for each hci dev*/
+static DEFINE_MUTEX(btmtk_chip_state_mutex);
+#define CHIP_STATE_MUTEX_LOCK() mutex_lock(&btmtk_chip_state_mutex)
+#define CHIP_STATE_MUTEX_UNLOCK() mutex_unlock(&btmtk_chip_state_mutex)
+static DEFINE_MUTEX(btmtk_fops_state_mutex);
+#define FOPS_MUTEX_LOCK() mutex_lock(&btmtk_fops_state_mutex)
+#define FOPS_MUTEX_UNLOCK() mutex_unlock(&btmtk_fops_state_mutex)
+
+#define SECTION_NUM_MAX 10
+
+/**
+ * Global parameters(mtkbt_)
+ */
+uint8_t btmtk_log_lvl = BTMTK_LOG_LVL_DEF;
+
+/* To support dynamic mount of interface can be probed */
+static int btmtk_intf_num = BT_MCU_MINIMUM_INTERFACE_NUM;
+/* To allow g_bdev being sized from btmtk_intf_num setting */
+static struct btmtk_dev **g_bdev;
+struct btmtk_dev *g_sbdev;
+
+/*btmtk main information*/
+static struct btmtk_main_info main_info;
+
+/* State machine table that clarify through each HIF events,
+ * To specify HIF event on
+ * Entering / End / Error
+ */
+
+#if (USE_DEVICE_NODE == 0)
+static const struct btmtk_cif_state g_cif_state[] = {
+ /* HIF_EVENT_PROBE */
+ {BTMTK_STATE_PROBE, BTMTK_STATE_WORKING, BTMTK_STATE_DISCONNECT},
+ /* HIF_EVENT_DISCONNECT */
+ {BTMTK_STATE_DISCONNECT, BTMTK_STATE_DISCONNECT, BTMTK_STATE_DISCONNECT},
+ /* HIF_EVENT_SUSPEND */
+ {BTMTK_STATE_SUSPEND, BTMTK_STATE_SUSPEND, BTMTK_STATE_FW_DUMP},
+ /* HIF_EVENT_RESUME */
+ {BTMTK_STATE_RESUME, BTMTK_STATE_WORKING, BTMTK_STATE_FW_DUMP},
+ /* HIF_EVENT_STANDBY */
+ {BTMTK_STATE_STANDBY, BTMTK_STATE_STANDBY, BTMTK_STATE_FW_DUMP},
+ /* HIF_EVENT_SUBSYS_RESET */
+ {BTMTK_STATE_SUBSYS_RESET, BTMTK_STATE_WORKING, BTMTK_STATE_FW_DUMP},
+ /* HIF_EVENT_WHOLE_CHIP_RESET */
+ {BTMTK_STATE_FW_DUMP, BTMTK_STATE_DISCONNECT, BTMTK_STATE_FW_DUMP},
+ /* HIF_EVENT_FW_DUMP */
+ {BTMTK_STATE_FW_DUMP, BTMTK_STATE_FW_DUMP, BTMTK_STATE_FW_DUMP},
+};
+#else //(USE_DEVICE_NODE == 1)
+static const struct btmtk_cif_state g_cif_state[] = {
+ /* HIF_EVENT_PROBE */
+ {BTMTK_STATE_PROBE, BTMTK_STATE_WORKING, BTMTK_STATE_ERR},
+ /* HIF_EVENT_DISCONNECT */
+ {BTMTK_STATE_DISCONNECT, BTMTK_STATE_DISCONNECT, BTMTK_STATE_DISCONNECT},
+ /* HIF_EVENT_SUSPEND */
+ {BTMTK_STATE_SUSPEND, BTMTK_STATE_SUSPEND, BTMTK_STATE_ERR},
+ /* HIF_EVENT_RESUME */
+ {BTMTK_STATE_RESUME, BTMTK_STATE_WORKING, BTMTK_STATE_ERR},
+ /* HIF_EVENT_STANDBY */
+ {BTMTK_STATE_STANDBY, BTMTK_STATE_STANDBY, BTMTK_STATE_ERR},
+ /* HIF_EVENT_SUBSYS_RESET */
+ {BTMTK_STATE_SUBSYS_RESET, BTMTK_STATE_WORKING, BTMTK_STATE_ERR},
+ /* HIF_EVENT_WHOLE_CHIP_RESET */
+ {BTMTK_STATE_FW_DUMP, BTMTK_STATE_CLOSED, BTMTK_STATE_ERR},
+ /* HIF_EVENT_FW_DUMP */
+ {BTMTK_STATE_FW_DUMP, BTMTK_STATE_CLOSED, BTMTK_STATE_ERR},
+};
+#endif
+__weak int btmtk_cif_register(void)
+{
+ BTMTK_WARN("weak function %s not implement", __func__);
+ return -1;
+}
+
+__weak int btmtk_cif_deregister(void)
+{
+ BTMTK_WARN("weak function %s not implement", __func__);
+ return -1;
+}
+
+__weak int btmtk_cif_send_calibration(struct btmtk_dev *bdev)
+{
+ BTMTK_WARN("weak function %s not implement", __func__);
+ return -1;
+}
+
+#if (USE_DEVICE_NODE == 1)
+int btmtk_cif_rx_packet_handler(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ return rx_skb_enqueue(skb);
+}
+#else
+__weak int btmtk_cif_rx_packet_handler(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ BTMTK_WARN("weak function %s not implement", __func__);
+ return -1;
+}
+#endif
+
+__weak int btmtk_send_apcf_reserved(struct btmtk_dev *bdev)
+{
+ BTMTK_WARN("weak function %s not implement", __func__);
+ return -1;
+}
+
+#if 0
+void btmtk_do_gettimeofday(struct timeval *tv)
+{
+#if (KERNEL_VERSION(4, 19, 85) > LINUX_VERSION_CODE)
+ do_gettimeofday(tv);
+#else
+ struct timespec64 ts;
+
+ ktime_get_real_ts64(&ts);
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec/1000;
+#endif
+}
+#endif
+void btmtk_getUTCtime(struct bt_utc_struct *utc)
+{
+#if (KERNEL_VERSION(4, 19, 85) > LINUX_VERSION_CODE)
+ struct timeval tv;
+
+ do_gettimeofday(&tv);
+ rtc_time_to_tm(tv.tv_sec, &utc->tm);
+ utc->usec = tv.tv_usec;
+#else
+ struct timespec64 ts;
+
+ ktime_get_real_ts64(&ts);
+ rtc_time64_to_tm(ts.tv_sec, &utc->tm);
+ utc->usec = ts.tv_nsec/1000;
+#endif
+ utc->tm.tm_year += 1900;
+ utc->tm.tm_mon += 1;
+}
+
+
+int32_t btmtk_intcmd_set_fw_log(uint8_t flag)
+{
+ u8 fw_log_cmd[8] = { 0x01, 0x5D, 0xFC, 0x04, 0x02, 0x00, 0x02, 0x03 };
+ int ret;
+
+ BTMTK_INFO("%s send flag[0x%02X]", __func__, flag);
+ fw_log_cmd[7] = flag;
+ ret = btmtk_main_send_cmd(g_sbdev,
+ fw_log_cmd, 8, NULL, 0,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0)
+ BTMTK_ERR("%s faill to send flag[0x%02X]", __func__, flag);
+
+ return ret;
+}
+
+void btmtk_get_UTC_time_str(char *ts_str)
+{
+ struct bt_utc_struct utc;
+
+ btmtk_getUTCtime(&utc);
+
+ memset(ts_str, 0, HCI_SNOOP_TS_STR_LEN);
+ (void)snprintf(ts_str, HCI_SNOOP_TS_STR_LEN,
+ "%04d%02d%02d-%02d%02d%02d.%06u",
+ utc.tm.tm_year, utc.tm.tm_mon, utc.tm.tm_mday,
+ utc.tm.tm_hour, utc.tm.tm_min, utc.tm.tm_sec, utc.usec);
+
+}
+
+/*get 1 byte only*/
+int btmtk_efuse_read(struct btmtk_dev *bdev, u16 addr, u8 *value)
+{
+ uint8_t efuse_r[READ_EFUSE_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x0E,
+ 0x01, 0x0D, 0x0A, 0x00, 0x02, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};/*4 sub block number(sub block 0~3)*/
+
+ uint8_t efuse_r_event[READ_EFUSE_EVT_HDR_LEN] = {0x04, 0xE4, 0x1E, 0x02, 0x0D, 0x1A, 0x00, 02, 04};
+ /*check event
+ *04 E4 LEN(1B) 02 0D LEN(2Byte) 02 04 ADDR(2Byte) VALUE(4B) ADDR(2Byte) VALUE(4Byte)
+ *ADDR(2Byte) VALUE(4B) ADDR(2Byte) VALUE(4Byte)
+ */
+ int ret = 0;
+ uint8_t sub_block_addr_in_event = 0;
+ uint16_t sub_block = (addr / 16) * 4;
+ uint8_t temp = 0;
+
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET] = sub_block & 0xFF;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 1] = (sub_block & 0xFF00) >> 8;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 2] = (sub_block + 1) & 0xFF;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 3] = ((sub_block + 1) & 0xFF00) >> 8;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 4] = (sub_block + 2) & 0xFF;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 5] = ((sub_block + 2) & 0xFF00) >> 8;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 6] = (sub_block + 3) & 0xFF;
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 7] = ((sub_block + 3) & 0xFF00) >> 8;
+
+ ret = btmtk_main_send_cmd(bdev,
+ efuse_r, READ_EFUSE_CMD_LEN,
+ efuse_r_event, READ_EFUSE_EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ if (ret) {
+ BTMTK_WARN("btmtk_main_send_cmd error");
+ return ret;
+ }
+
+ if (memcmp(bdev->io_buf, efuse_r_event, READ_EFUSE_EVT_HDR_LEN) == 0) {
+ /*compare rxbuf format ok, compare addr*/
+ BTMTK_DBG("compare rxbuf format ok");
+ if (efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET] == bdev->io_buf[9] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 1] == bdev->io_buf[10] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 2] == bdev->io_buf[15] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 3] == bdev->io_buf[16] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 4] == bdev->io_buf[21] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 5] == bdev->io_buf[22] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 6] == bdev->io_buf[27] &&
+ efuse_r[READ_EFUSE_CMD_BLOCK_OFFSET + 7] == bdev->io_buf[28]) {
+
+ BTMTK_DBG("address compare ok");
+ /*Get value*/
+ sub_block_addr_in_event = ((addr / 16) / 4);/*cal block num*/
+ temp = addr % 16;
+ BTMTK_DBG("address in block %d", temp);
+ switch (temp) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ *value = bdev->io_buf[11 + temp];
+ break;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ *value = bdev->io_buf[17 + temp - 4];
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ *value = bdev->io_buf[23 + temp - 8];
+ break;
+
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ *value = bdev->io_buf[29 + temp - 12];
+ break;
+ }
+ } else {
+ BTMTK_WARN("address compare fail");
+ ret = -1;
+ }
+ } else {
+ BTMTK_WARN("compare rxbuf format fail");
+ ret = -1;
+ }
+
+ return ret;
+}
+void btmtk_free_fw_cfg_struct(struct fw_cfg_struct *fw_cfg, int count)
+{
+ int i = 0;
+
+ for (i = 0; i < count; i++) {
+ if (fw_cfg[i].content) {
+ BTMTK_INFO("%s:kfree %d", __func__, i);
+ kfree(fw_cfg[i].content);
+ fw_cfg[i].content = NULL;
+ fw_cfg[i].length = 0;
+ } else
+ fw_cfg[i].length = 0;
+ }
+}
+
+void btmtk_free_setting_file(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s begin", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev == NULL", __func__);
+ return;
+ }
+
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.picus_filter, 1);
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.picus_enable, 1);
+ btmtk_free_fw_cfg_struct(bdev->bt_cfg.phase1_wmt_cmd, PHASE1_WMT_CMD_COUNT);
+ btmtk_free_fw_cfg_struct(bdev->bt_cfg.vendor_cmd, VENDOR_CMD_COUNT);
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.audio_cmd, 1);
+
+ memset(&bdev->bt_cfg, 0, sizeof(bdev->bt_cfg));
+ /* reset pin initial value need to be -1, used to judge after
+ * disconnected before probe, can't do chip reset
+ */
+ bdev->bt_cfg.dongle_reset_gpio_pin = -1;
+}
+
+static void btmtk_initialize_cfg_items(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s begin", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+
+ bdev->bt_cfg.dongle_reset_gpio_pin = 220;
+ bdev->bt_cfg.support_dongle_reset = 0;
+ bdev->bt_cfg.support_full_fw_dump = 0;
+ bdev->bt_cfg.support_unify_woble = 1;
+ bdev->bt_cfg.unify_woble_type = 0;
+ bdev->bt_cfg.support_woble_by_eint = 0;
+ bdev->bt_cfg.support_woble_for_bt_disable = 0;
+ bdev->bt_cfg.support_woble_wakelock = 0;
+ bdev->bt_cfg.reset_stack_after_woble = 0;
+ bdev->bt_cfg.support_auto_picus = 0;
+ bdev->bt_cfg.support_picus_to_host = 0;
+ bdev->bt_cfg.support_bt_single_sku = 0;
+ bdev->bt_cfg.support_audio_setting = 0;
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.picus_filter, 1);
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.picus_enable, 1);
+ btmtk_free_fw_cfg_struct(bdev->bt_cfg.phase1_wmt_cmd, PHASE1_WMT_CMD_COUNT);
+ btmtk_free_fw_cfg_struct(bdev->bt_cfg.vendor_cmd, VENDOR_CMD_COUNT);
+ btmtk_free_fw_cfg_struct(&bdev->bt_cfg.audio_cmd, 1);
+
+ BTMTK_INFO("%s end", __func__);
+}
+
+u8 btmtk_get_chip_state(struct btmtk_dev *bdev)
+{
+ u8 state = BTMTK_STATE_INIT;
+
+ CHIP_STATE_MUTEX_LOCK();
+ if (bdev)
+ state = bdev->interface_state;
+ else
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ CHIP_STATE_MUTEX_UNLOCK();
+
+ return state;
+}
+
+void btmtk_set_chip_state(struct btmtk_dev *bdev, u8 new_state)
+{
+ static const char * const state_msg[BTMTK_STATE_MSG_NUM] = {
+ "UNKNOWN", "INIT", "DISCONNECT", "PROBE", "WORKING", "SUSPEND", "RESUME",
+ "FW_DUMP", "STANDBY", "SUBSYS_RESET", "SEND_ASSERT", "CLOSED", "ERROR"
+ };
+
+ if (new_state >= BTMTK_STATE_MSG_NUM) {
+ BTMTK_INFO("%s: new_state invalid(%d)", __func__, new_state);
+ return;
+ }
+
+ BTMTK_INFO("%s: %s(%d) -> %s(%d) dongle_index[%d]", __func__, state_msg[bdev->interface_state],
+ bdev->interface_state, state_msg[new_state], new_state, bdev->dongle_index);
+
+ CHIP_STATE_MUTEX_LOCK();
+ bdev->interface_state = new_state;
+ CHIP_STATE_MUTEX_UNLOCK();
+}
+
+u8 btmtk_fops_get_state(struct btmtk_dev *bdev)
+{
+ u8 state = BTMTK_FOPS_STATE_INIT;
+
+ FOPS_MUTEX_LOCK();
+ if (bdev)
+ state = bdev->fops_state;
+ else
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ FOPS_MUTEX_UNLOCK();
+
+ return state;
+}
+
+static void btmtk_fops_set_state(struct btmtk_dev *bdev, u8 new_state)
+{
+ static const char * const fstate_msg[BTMTK_FOPS_STATE_MSG_NUM] = {
+ "UNKNOWN", "INIT", "OPENING", "OPENED", "CLOSING", "CLOSED",
+ };
+
+ if (new_state >= BTMTK_FOPS_STATE_MSG_NUM) {
+ BTMTK_INFO("%s: new_state invalid(%d)", __func__, new_state);
+ return;
+ }
+
+ BTMTK_INFO("%s: FOPS_%s(%d) -> FOPS_%s(%d)", __func__, fstate_msg[bdev->fops_state],
+ bdev->fops_state, fstate_msg[new_state], new_state);
+ FOPS_MUTEX_LOCK();
+ bdev->fops_state = new_state;
+ FOPS_MUTEX_UNLOCK();
+#if (USE_DEVICE_NODE == 1)
+ if (main_info.hif_hook.fw_log_state)
+ main_info.hif_hook.fw_log_state(new_state);
+#endif
+}
+
+void *btmtk_kallsyms_lookup_name(const char *name)
+{
+ void *addr = __symbol_get(name);
+
+ if (addr) {
+#ifdef CONFIG_ARM
+#ifdef CONFIG_THUMB2_KERNEL
+ /* set bit 0 in address for thumb mode */
+ addr |= 1;
+#endif
+#endif
+ __symbol_put(name);
+ }
+ return addr;
+}
+
+static void btmtk_main_info_initialize(void)
+{
+ u32 snoop_idx = 0;
+
+ memset(&main_info, 0, sizeof(main_info));
+ for (snoop_idx = 0; snoop_idx < HCI_SNOOP_TYPE_MAX; snoop_idx++)
+ main_info.snoop[snoop_idx].index = HCI_SNOOP_ENTRY_NUM - 1;
+ atomic_set(&main_info.chip_reset, BTMTK_RESET_DONE);
+ atomic_set(&main_info.subsys_reset, BTMTK_RESET_DONE);
+
+#if defined(CONFIG_MP_WAKEUP_SOURCE_SYSFS_STAT)
+ main_info.fwdump_ws = wakeup_source_register(NULL, "btmtk_fwdump_wakelock");
+ main_info.woble_ws = wakeup_source_register(NULL, "btmtk_woble_wakelock");
+ main_info.eint_ws = wakeup_source_register(NULL, "btevent_eint");
+#if WAKEUP_BT_IRQ
+ main_info.irq_ws = wakeup_source_register(NULL, "btevent_irq");
+#endif
+#elif (defined(LINUX_OS) && (KERNEL_VERSION(5, 4, 0) > LINUX_VERSION_CODE)) || \
+ (defined(ANDROID_OS) && (KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE))
+ main_info.fwdump_ws = wakeup_source_register("btmtk_fwdump_wakelock");
+ main_info.woble_ws = wakeup_source_register("btmtk_woble_wakelock");
+ main_info.eint_ws = wakeup_source_register("btevent_eint");
+#if WAKEUP_BT_IRQ
+ main_info.irq_ws = wakeup_source_register("btevent_irq");
+#endif
+#else
+ main_info.fwdump_ws = wakeup_source_register(NULL, "btmtk_fwdump_wakelock");
+ main_info.woble_ws = wakeup_source_register(NULL, "btmtk_woble_wakelock");
+ main_info.eint_ws = wakeup_source_register(NULL, "btevent_eint");
+#if WAKEUP_BT_IRQ
+ main_info.irq_ws = wakeup_source_register(NULL, "btevent_irq");
+#endif
+#endif
+
+ main_info.wmt_over_hci_header[0] = HCI_COMMAND_PKT;
+ main_info.wmt_over_hci_header[1] = 0x6F;
+ main_info.wmt_over_hci_header[2] = 0xFC;
+
+ main_info.read_iso_packet_size_cmd[0] = HCI_COMMAND_PKT;
+ main_info.read_iso_packet_size_cmd[1] = 0x98;
+ main_info.read_iso_packet_size_cmd[2] = 0xFD;
+ main_info.read_iso_packet_size_cmd[3] = 0x02;
+
+}
+
+struct btmtk_main_info *btmtk_get_main_info(void)
+{
+ return &main_info;
+}
+
+int btmtk_get_interface_num(void)
+{
+ return btmtk_intf_num;
+}
+
+struct btmtk_dev **btmtk_get_pp_bdev(void)
+{
+ return g_bdev;
+}
+
+void btmtk_free_debug_reg_struct(struct debug_reg_struct *debug_reg)
+{
+ int i = 0;
+ int count = debug_reg->num;
+
+ for (i = 0; i < count; i++) {
+ if (debug_reg->reg[i].content) {
+ BTMTK_DBG("%s:kfree %d", __func__, i);
+ kfree(debug_reg->reg[i].content);
+ debug_reg->reg[i].content = NULL;
+ debug_reg->reg[i].length = 0;
+ } else {
+ debug_reg->reg[i].length = 0;
+ }
+ }
+
+ kfree(debug_reg->reg);
+ debug_reg->reg = NULL;
+ debug_reg->num = 0;
+}
+
+static void btmtk_initialize_debug_reg_items(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s begin", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+#if (USE_DEVICE_NODE == 0)
+ btmtk_clean_debug_reg_file(bdev);
+#endif
+ bdev->debug_sop_reg_dump.num = 0;
+ BTMTK_INFO("%s end", __func__);
+}
+
+void btmtk_clean_debug_reg_file(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s begin", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev == NULL", __func__);
+ return;
+ }
+
+ btmtk_free_debug_reg_struct(&bdev->debug_sop_reg_dump);
+ BTMTK_INFO("%s end", __func__);
+}
+
+int btmtk_load_register(char *block_name, struct debug_reg_struct *save_reg,
+ u8 *searchcontent, enum debug_reg_index_len index_length)
+{
+ int ret = 0, i = 0;
+ u16 temp_len = 0;
+ u32 temp[DEBUG_REG_NUM]; /* save for total hex number */
+ unsigned long parsing_result = 0;
+ char *search_result = NULL;
+ char *search_end = NULL;
+ char search[SEARCH_LEN];
+ char *next_block = NULL;
+ char number[DEBUG_REG_SIZE + 1]; /* 1 is for '\0' */
+ char *regnum = NULL;
+
+ memset(search, 0, SEARCH_LEN);
+ memset(temp, 0, DEBUG_REG_NUM * sizeof(u32));
+ memset(number, 0, DEBUG_REG_SIZE + 1);
+
+ if (searchcontent == NULL) {
+ BTMTK_ERR("%s: Searchcontent is NULL", __func__);
+ return -1;
+ }
+
+ /* search REG NUM */
+ (void)snprintf(search, SEARCH_LEN, "%sNUM:", block_name);
+ search_result = strstr((char *)searchcontent, search);
+ if (search_result) {
+ search_result = strstr(search_result, ":");
+ if (search_result == NULL) {
+ BTMTK_ERR("%s:regnum Incorrect format", __func__);
+ return -1;
+ }
+
+ search_end = strstr(search_result, ",");
+ if (search_end == NULL) {
+ BTMTK_ERR("%s: regnum is NULL", __func__);
+ return -1;
+ }
+
+ if (search_end - search_result < 0) {
+ BTMTK_ERR("%s: Incorrect Format in %s", __func__, search);
+ return -1;
+ }
+ regnum = kzalloc((search_end - search_result) * sizeof(char), GFP_KERNEL);
+ if (regnum == NULL) {
+ BTMTK_ERR("%s: Allocate memory fail", __func__);
+ return -ENOMEM;
+ }
+
+ memset(regnum, 0, search_end - search_result);
+ memcpy(regnum, search_result + 1, search_end - search_result - 1);
+ regnum[search_end - search_result - 1] = '\0';
+ ret = kstrtoul(regnum, 0, &parsing_result);
+ if (ret != 0) {
+ BTMTK_ERR("%s: %s kstrtoul fail: %d", __func__, regnum, ret);
+ return -ENOMEM;
+ }
+
+ save_reg->reg = (struct debug_reg *)kzalloc(parsing_result * sizeof(struct debug_reg), GFP_KERNEL);
+ if (save_reg->reg == NULL) {
+ BTMTK_ERR("%s: Allocate memory fail", __func__);
+ return -ENOMEM;
+ }
+ save_reg->num = (u32)parsing_result;
+ BTMTK_INFO("%s: reg num is %d", __func__, save_reg->num);
+ } else{
+ BTMTK_ERR("%s: %s is not found", __func__, search);
+ return ret;
+ }
+
+ /* search block name */
+ for (i = 0; i < save_reg->num; i++) {
+ temp_len = 0;
+ if (index_length == DEBUG_REG_INX_LEN_2) /* EX: POWER_STATUS_01 */
+ (void)snprintf(search, SEARCH_LEN, "%s%02d:", block_name, i);
+ else if (index_length == DEBUG_REG_INX_LEN_3) /* EX: POWER_STATUS_001 */
+ (void)snprintf(search, SEARCH_LEN, "%s%03d:", block_name, i);
+ else
+ (void)snprintf(search, SEARCH_LEN, "%s:", block_name);
+
+ ret = 0;
+
+ search_result = strstr((char *)searchcontent, search);
+ if (search_result) {
+ memset(temp, 0, DEBUG_REG_NUM * sizeof(u32));
+ search_result = strstr(search_result, "0x");
+ if (search_result == NULL) {
+ BTMTK_ERR("%s: search_result is NULL", __func__);
+ return -1;
+ }
+
+ /* find next line as end of this command line, if NULL means last line */
+ next_block = strstr(search_result, ":");
+ if (next_block == NULL)
+ BTMTK_WARN("%s: if NULL means last line", __func__);
+
+ do {
+ search_end = strstr(search_result, ",");
+ if (search_end == NULL) {
+ BTMTK_ERR("%s: Search_end is NULL", __func__);
+ break;
+ }
+
+ if (search_end - search_result != DEBUG_REG_SIZE) {
+ BTMTK_ERR("%s: Incorrect Format in %s", __func__, search);
+ break;
+ }
+
+ memset(number, 0, DEBUG_REG_SIZE + 1);
+ memcpy(number, search_result, DEBUG_REG_SIZE);
+ ret = kstrtoul(number, 0, &parsing_result);
+ if (ret == 0) {
+ if (temp_len >= DEBUG_REG_NUM) {
+ BTMTK_ERR("%s: %s data over %d", __func__, search, DEBUG_REG_NUM);
+ break;
+ }
+ temp[temp_len] = parsing_result;
+ temp_len++;
+ } else {
+ BTMTK_WARN("%s: %s kstrtoul fail: %d", __func__, search, ret);
+ break;
+ }
+ search_result = strstr(search_end, "0x");
+ if (search_result == NULL) {
+ BTMTK_ERR("%s: search_result is NULL", __func__);
+ break;
+ }
+ } while (search_result < next_block || (search_result && next_block == NULL));
+ } else
+ BTMTK_DBG("%s: %s is not found in %d", __func__, search, i);
+
+ if (temp_len && temp_len < DEBUG_REG_NUM) {
+ BTMTK_DBG("%s: %s found & stored in %d", __func__, search, i);
+ save_reg->reg[i].content = (u32 *)kzalloc(temp_len * sizeof(u32), GFP_KERNEL);
+ if (save_reg->reg[i].content == NULL) {
+ BTMTK_ERR("%s: Allocate memory fail(%d)", __func__, i);
+ return -ENOMEM;
+ }
+ memcpy(save_reg->reg[i].content, temp, temp_len * sizeof(u32));
+ save_reg->reg[i].length = temp_len;
+ BTMTK_DBG("%s: %s has found %d stored , value0 is %08x",
+ __func__, block_name, temp_len, temp[0]);
+ }
+ }
+
+ return ret;
+}
+
+void btmtk_load_debug_sop_register(char *debug_sop_name, struct device *dev, struct btmtk_dev *bdev)
+{
+ int err;
+ u32 code_len = 0;
+
+ btmtk_initialize_debug_reg_items(bdev);
+
+ err = btmtk_load_code_from_setting_files(debug_sop_name, dev, &code_len, bdev);
+ if (err) {
+ BTMTK_WARN("btmtk_load_code_from_setting_files failed!!");
+
+#ifdef DEBUG_SOP_NAME
+ /* load from sdio_debug.h */
+ if (is_mt7902(bdev->chip_id)) {
+ err = btmtk_load_register("DEBUG_REG_",
+ &bdev->debug_sop_reg_dump, DEBUG_SOP_NAME(7902), DEBUG_REG_INX_LEN_3);
+ if (err)
+ goto LOAD_END;
+ }
+ if (is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id)) {
+ err = btmtk_load_register("DEBUG_REG_",
+ &bdev->debug_sop_reg_dump, DEBUG_SOP_NAME(7921), DEBUG_REG_INX_LEN_3);
+ if (err)
+ goto LOAD_END;
+ }
+ BTMTK_INFO("btmtk_load_debug_sop_register from .h!!");
+#endif
+ } else {
+ /* load from file */
+ err = btmtk_load_register("DEBUG_REG_",
+ &bdev->debug_sop_reg_dump, bdev->setting_file, DEBUG_REG_INX_LEN_3);
+ if (err)
+ goto LOAD_END;
+ BTMTK_INFO("btmtk_load_debug_sop_register from .bin!!");
+ }
+
+LOAD_END:
+ /* release setting file memory */
+ kfree(bdev->setting_file);
+ bdev->setting_file = NULL;
+
+ if (err)
+ BTMTK_ERR("%s: error return %d", __func__, err);
+}
+
+void btmtk_hci_snoop_print_to_log(void)
+{
+ u8 counter, index, snoop_index;
+ char *snoop_str[HCI_SNOOP_TYPE_MAX] = {
+ "Command from stack",
+ "Command to FW",
+ "Event to stack",
+ "Event From FW",
+ "ADV Event to stack",
+ "ADV Event From FW",
+ "NOCP Event to stack",
+ "NOCP Event From FW",
+ "TX ACL from stack",
+ "TX ACL to FW",
+ "RX ACL to stack",
+ "RX ACL From FW",
+ "TX ISO from stack",
+ "TX ISO to FW",
+ "RX ISO to stack",
+ "RX ISO From FW"};
+
+ for (snoop_index = 0; snoop_index < HCI_SNOOP_TYPE_MAX; snoop_index++) {
+ BTMTK_INFO("HCI %s Dump: Using A5 A5 to separator the head 32 bytes and the tail 32 bytes data",
+ snoop_str[snoop_index]);
+ if (main_info.snoop[snoop_index].index >= (HCI_SNOOP_ENTRY_NUM - 1))
+ index = 0;
+ else
+ index = main_info.snoop[snoop_index].index + 1;
+ for (counter = 0; counter < HCI_SNOOP_ENTRY_NUM; counter++) {
+ if (main_info.snoop[snoop_index].len[index] > 0)
+ BTMTK_INFO_RAW(main_info.snoop[snoop_index].buf[index],
+ main_info.snoop[snoop_index].len[index],
+ "time(%s)-act_len(%d)-len(%d):", main_info.snoop[snoop_index].timestamp[index],
+ main_info.snoop[snoop_index].actual_len[index],
+ main_info.snoop[snoop_index].len[index]);
+ index++;
+ if (index >= HCI_SNOOP_ENTRY_NUM)
+ index = 0;
+ }
+ }
+}
+
+void btmtk_hci_snoop_save(unsigned int type, u8 *buf, u32 len)
+{
+ u32 copy_len = HCI_SNOOP_BUF_SIZE;
+ u32 copy_tail_len = HCI_SNOOP_BUF_SIZE;
+ u8 separator_char[SEPARATOR_LEN] = {0xA5, 0xA5};
+ u8 *copy_tail_buf;
+
+ if (!buf || len == 0 || type >= HCI_SNOOP_TYPE_MAX) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return;
+ }
+
+ if (main_info.snoop[type].index < HCI_SNOOP_ENTRY_NUM) {
+ if (len < HCI_SNOOP_BUF_SIZE) {
+ copy_len = len;
+ copy_tail_len = 0;
+ } else if (len > HCI_SNOOP_BUF_SIZE && len <= HCI_SNOOP_BUF_SIZE * 2)
+ copy_tail_len = len - copy_len;
+
+ main_info.snoop[type].len[main_info.snoop[type].index] = copy_len & 0xff;
+ main_info.snoop[type].actual_len[main_info.snoop[type].index] = len & 0xffff;
+ btmtk_get_UTC_time_str(main_info.snoop[type].timestamp[main_info.snoop[type].index]);
+ memset(main_info.snoop[type].buf[main_info.snoop[type].index], 0, HCI_SNOOP_MAX_BUF_SIZE);
+ memcpy(main_info.snoop[type].buf[main_info.snoop[type].index], buf, copy_len & 0xff);
+ /* save less then 32 bytes data in the buffer tail, using A5 A5 to
+ * separator the head 32 bytes data and the tail 32 bytes data
+ */
+ if (copy_tail_len > 0) {
+ copy_tail_buf = buf + len - copy_tail_len;
+ main_info.snoop[type].len[main_info.snoop[type].index] +=
+ (copy_tail_len + SEPARATOR_LEN) & 0xff;
+ memcpy(main_info.snoop[type].buf[main_info.snoop[type].index] + copy_len, separator_char,
+ SEPARATOR_LEN);
+ memcpy(main_info.snoop[type].buf[main_info.snoop[type].index] + copy_len + SEPARATOR_LEN,
+ copy_tail_buf, copy_tail_len);
+ }
+
+ if (main_info.snoop[type].index == 0)
+ main_info.snoop[type].index = HCI_SNOOP_ENTRY_NUM;
+ main_info.snoop[type].index--;
+ }
+}
+
+void btmtk_hci_snoop_print(const u8 *buf, u32 len)
+{
+ u32 copy_len = HCI_SNOOP_BUF_SIZE;
+ u32 copy_tail_len = HCI_SNOOP_BUF_SIZE;
+ u8 separator_char[SEPARATOR_LEN] = {0xA5, 0xA5};
+ const u8 *copy_tail_buf;
+ u8 hci_snoop_buf[HCI_SNOOP_MAX_BUF_SIZE] = {0};
+ u16 hci_snoop_len = 0;
+
+ if (buf && len > 0) {
+ if (len < HCI_SNOOP_BUF_SIZE) {
+ copy_len = len;
+ copy_tail_len = 0;
+ } else if (len > HCI_SNOOP_BUF_SIZE && len <= HCI_SNOOP_BUF_SIZE * 2)
+ copy_tail_len = len - copy_len;
+
+ memcpy(hci_snoop_buf, buf, copy_len & 0xff);
+ hci_snoop_len = copy_len & 0xff;
+
+ /* save less then 32 bytes data in the buffer tail, using A5 A5 to
+ * separator the head 32 bytes data and the tail 32 bytes data
+ */
+ if (copy_tail_len > 0) {
+ copy_tail_buf = buf + len - copy_tail_len;
+ hci_snoop_len += (copy_tail_len + SEPARATOR_LEN) & 0xff;
+ memcpy(hci_snoop_buf + copy_len, separator_char, SEPARATOR_LEN);
+ memcpy(hci_snoop_buf + copy_len + SEPARATOR_LEN,
+ copy_tail_buf, copy_tail_len);
+ }
+
+ if (hci_snoop_len > 0)
+ BTMTK_INFO_RAW(hci_snoop_buf, hci_snoop_len, "act_len(%d)-len(%d)-buf(%p):",
+ len, hci_snoop_len, buf);
+ }
+}
+
+/* HCI receive mechnism */
+static inline struct sk_buff *h4_recv_buf(struct hci_dev *hdev,
+ struct sk_buff *skb,
+ const unsigned char *buffer,
+ int count,
+ const struct h4_recv_pkt *pkts,
+ int pkts_count)
+{
+ struct btmtk_dev *bdev = NULL;
+ /* used for print debug log*/
+ const unsigned char *buffer_dbg = buffer;
+ int count_dbg = count;
+ unsigned char *skb_tmp = NULL;
+
+ if (hdev == NULL || buffer == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s, bdev is invalid", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+ /* Check for error from previous call */
+ if (IS_ERR(skb))
+ skb = NULL;
+ /* BTMTK_DBG("%s, buffer[0]=0x%02X, count[%d], pkts_count[%d]", __func__, *buffer, count, pkts_count); */
+
+ while (count) {
+ int i, len;
+
+ if (!skb) {
+ for (i = 0; i < pkts_count; i++) {
+ if (buffer[0] != (&pkts[i])->type)
+ continue;
+
+ skb = bt_skb_alloc((&pkts[i])->maxlen,
+ GFP_ATOMIC);
+ if (!skb) {
+ BTMTK_ERR("%s, alloc skb failed!", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ hci_skb_pkt_type(skb) = (&pkts[i])->type;
+ hci_skb_expect(skb) = (&pkts[i])->hlen;
+ break;
+ }
+
+ /* Check for invalid packet type */
+ if (!skb) {
+ BTMTK_ERR("%s,skb is invalid, buffer[0] = 0x%02X, count[%d]", __func__,
+ buffer[0], count);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ else {
+ btmtk_hci_snoop_print(buffer_dbg, count_dbg);
+ btmtk_hci_snoop_print(buffer, count);
+ btmtk_hci_snoop_print_to_log();
+ }
+ return ERR_PTR(-EILSEQ);
+ }
+
+ count -= 1;
+ buffer += 1;
+ }
+
+ len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
+ /* BTMTK_DBG("%s, hci_skb_expect[%d](hdlen), skb->len[%d], count[%d]", __func__, hci_skb_expect(skb), skb->len, count); */
+
+ skb_tmp = skb_put(skb, len);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed. Len = %d!", __func__, len);
+ kfree_skb(skb);
+ return ERR_PTR(-ENOMEM);
+ }
+ memcpy(skb_tmp, buffer, len);
+ /* If kernel version > 4.x */
+ /* skb_put_data(skb, buffer, len); */
+
+ count -= len;
+ buffer += len;
+
+ /* Check for partial packet */
+ if (skb->len < hci_skb_expect(skb))
+ continue;
+
+ for (i = 0; i < pkts_count; i++) {
+ if (hci_skb_pkt_type(skb) == (&pkts[i])->type)
+ break;
+ }
+
+ if (i >= pkts_count) {
+ BTMTK_ERR("%s, pkt type is invalid!", __func__);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ else {
+ btmtk_hci_snoop_print(buffer_dbg, count_dbg);
+ btmtk_hci_snoop_print(buffer, count);
+ btmtk_hci_snoop_print_to_log();
+ }
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ /* not use hci_skb_expect instead of hlen */
+ /* because hci_skb_expect will update by +=dlen */
+ if (skb->len == (&pkts[i])->hlen) {
+ u16 dlen;
+
+ /* BTMTK_DBG("%s begin, skb->len = %d, %d, %d", __func__, skb->len, */
+ /* (&pkts[i])->hlen, (&pkts[i])->lsize); */
+ switch ((&pkts[i])->lsize) {
+ case 0:
+ /* No variable data length */
+ dlen = 0;
+ break;
+ case 1:
+ /* Single octet variable length */
+ dlen = skb->data[(&pkts[i])->loff];
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ BTMTK_ERR("%s, maxlen[%d] skb_tailroom[%d] is not enough, dlen:%d!",
+ __func__, (&pkts[i])->maxlen, skb_tailroom(skb), dlen);
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s, send, len = %d", __func__, skb->len);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ else {
+ btmtk_hci_snoop_print(skb->data, skb->len);
+ btmtk_hci_snoop_print(buffer_dbg, count_dbg);
+ btmtk_hci_snoop_print(buffer, count);
+ btmtk_hci_snoop_print_to_log();
+ }
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ case 2:
+ /* Double octet variable length */
+ dlen = get_unaligned_le16(skb->data +
+ (&pkts[i])->loff);
+ /* parse ISO packet len*/
+ if ((&pkts[i])->type == HCI_ISODATA_PKT) {
+ unsigned char *cp = (unsigned char *)&dlen + 1;
+ *cp = *cp & 0x3F;
+ }
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ BTMTK_ERR("%s, maxlen[%d] skb_tailroom[%d] is not enough, dlen:%d!",
+ __func__, (&pkts[i])->maxlen, skb_tailroom(skb), dlen);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ else {
+ btmtk_hci_snoop_print(skb->data, skb->len);
+ btmtk_hci_snoop_print(buffer_dbg, count_dbg);
+ btmtk_hci_snoop_print(buffer, count);
+ btmtk_hci_snoop_print_to_log();
+ }
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ default:
+ /* Unsupported variable length */
+ BTMTK_ERR("%s, Unsupported variable length!", __func__);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ else {
+ btmtk_hci_snoop_print(buffer_dbg, count_dbg);
+ btmtk_hci_snoop_print(buffer, count);
+ btmtk_hci_snoop_print_to_log();
+ }
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ if (!dlen) {
+ /* No more data, complete frame */
+ (&pkts[i])->recv(hdev, skb);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ skb = NULL;
+ }
+ } else {
+ /* Complete frame */
+ (&pkts[i])->recv(hdev, skb);
+ if (is_mt66xx(bdev->chip_id))
+ btmtk_set_sleep(hdev, FALSE);
+ skb = NULL;
+ }
+ }
+
+ return skb;
+}
+
+static const struct h4_recv_pkt mtk_recv_pkts[] = {
+ { H4_RECV_ACL, .recv = btmtk_recv_acl },
+ { H4_RECV_SCO, .recv = hci_recv_frame },
+ { H4_RECV_EVENT, .recv = btmtk_recv_event },
+ { H4_RECV_ISO, .recv = btmtk_recv_iso },
+#if (USE_DEVICE_NODE == 1)
+ { H4_RECV_RHW_READ, .recv = btmtk_recv_rhw},
+ { H4_RECV_RHW_WRITE, .recv = btmtk_recv_rhw},
+#endif
+};
+
+#if ENABLESTP
+static inline struct sk_buff *mtk_add_stp(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ struct mtk_stp_hdr *shdr;
+ int dlen, err = 0, type = 0;
+ u8 stp_crc[STP_CRC_LEN] = {0x00, 0x00};
+
+ if (unlikely(skb_headroom(skb) < sizeof(*shdr)) ||
+ (skb_tailroom(skb) < MTK_STP_TLR_SIZE)) {
+ BTMTK_DBG("%s, add pskb_expand_head, headroom = %d, tailroom = %d",
+ __func__, skb_headroom(skb), skb_tailroom(skb));
+
+ err = pskb_expand_head(skb, sizeof(*shdr), MTK_STP_TLR_SIZE,
+ GFP_ATOMIC);
+ }
+ dlen = skb->len;
+ shdr = (void *) skb_push(skb, sizeof(*shdr));
+ shdr->prefix = 0x80;
+ shdr->dlen = cpu_to_be16((dlen & 0x0fff) | (type << 12));
+ shdr->cs = 0;
+/* Add the STP trailer
+ * kernel version > 4.20
+ * skb_put_zero(skb, MTK_STP_TLR_SIZE);
+ * kernel version < 4.20
+ */
+ skb_put(skb, STP_CRC_LEN);
+
+ return skb;
+}
+
+static const unsigned char *
+mtk_stp_split(struct btmtk_dev *bdev, const unsigned char *data, int count,
+ int *sz_h4)
+{
+ struct mtk_stp_hdr *shdr;
+
+ /* The cursor is reset when all the data of STP is consumed out */
+ if (!bdev->stp_dlen && bdev->stp_cursor >= 6) {
+ bdev->stp_cursor = 0;
+ BTMTK_ERR("reset cursor = %d\n", bdev->stp_cursor);
+ }
+
+ /* Filling pad until all STP info is obtained */
+ while (bdev->stp_cursor < 6 && count > 0) {
+ bdev->stp_pad[bdev->stp_cursor] = *data;
+ pr_err("fill stp format (%02x, %d, %d)\n",
+ bdev->stp_pad[bdev->stp_cursor], bdev->stp_cursor, count);
+ bdev->stp_cursor++;
+ data++;
+ count--;
+ }
+
+ /* Retrieve STP info and have a sanity check */
+ if (!bdev->stp_dlen && bdev->stp_cursor >= 6) {
+ shdr = (struct mtk_stp_hdr *)&bdev->stp_pad[2];
+ bdev->stp_dlen = be16_to_cpu(shdr->dlen) & 0x0fff;
+ pr_err("stp format (%02x, %02x)",
+ shdr->prefix, bdev->stp_dlen);
+
+ /* Resync STP when unexpected data is being read */
+ if (shdr->prefix != 0x80 || bdev->stp_dlen > 2048) {
+ BTMTK_ERR("stp format unexpect (%02x, %02x)",
+ shdr->prefix, bdev->stp_dlen);
+ BTMTK_ERR("reset cursor = %d\n", bdev->stp_cursor);
+ bdev->stp_cursor = 2;
+ bdev->stp_dlen = 0;
+ }
+ }
+
+ /* Directly quit when there's no data found for H4 can process */
+ if (count <= 0)
+ return NULL;
+
+ /* Tranlate to how much the size of data H4 can handle so far */
+ *sz_h4 = min_t(int, count, bdev->stp_dlen);
+
+ /* Update the remaining size of STP packet */
+ bdev->stp_dlen -= *sz_h4;
+
+ /* Data points to STP payload which can be handled by H4 */
+ return data;
+}
+#endif
+
+int btmtk_recv(struct hci_dev *hdev, const u8 *data, size_t count)
+{
+ struct btmtk_dev *bdev = NULL;
+ const unsigned char *p_left = data;
+ int sz_left = count;
+ int err;
+#if ENABLESTP
+ const unsigned char **p_h4 = NULL;
+ int sz_h4 = 0, adv = 0;
+#endif
+
+ if (hdev == NULL || data == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s, bdev is NULL!", __func__);
+ return -EINVAL;
+ }
+
+ while (sz_left > 0) {
+ /* The serial data received from MT7622 BT controller is
+ * at all time padded around with the STP header and tailer.
+ *
+ * A full STP packet is looking like
+ * -----------------------------------
+ * | STP header | H:4 | STP tailer |
+ * -----------------------------------
+ * but it doesn't guarantee to contain a full H:4 packet which
+ * means that it's possible for multiple STP packets forms a
+ * full H:4 packet that means extra STP header + length doesn't
+ * indicate a full H:4 frame, things can fragment. Whose length
+ * recorded in STP header just shows up the most length the
+ * H:4 engine can handle currently.
+ */
+#if ENABLESTP
+ p_h4 = mtk_stp_split(bdev, p_left, sz_left, &sz_h4);
+ if (!p_h4)
+ break;
+
+ adv = p_h4 - p_left;
+ sz_left -= adv;
+ p_left += adv;
+#endif
+
+#if ENABLESTP
+ bdev->rx_skb = h4_recv_buf(hdev, bdev->rx_skb, p_h4,
+ sz_h4, mtk_recv_pkts,
+ ARRAY_SIZE(mtk_recv_pkts));
+#else
+ bdev->rx_skb = h4_recv_buf(hdev, bdev->rx_skb, data,
+ count, mtk_recv_pkts,
+ ARRAY_SIZE(mtk_recv_pkts));
+#endif
+
+ if (IS_ERR(bdev->rx_skb)) {
+ err = PTR_ERR(bdev->rx_skb);
+ //BTMTK_ERR("Frame reassembly failed (%d)", err);
+ bdev->rx_skb = NULL;
+ return err;
+ }
+
+#if ENABLESTP
+ sz_left -= sz_h4;
+ p_left += sz_h4;
+#else
+ sz_left -= count;
+ p_left += count;
+#endif
+ }
+
+ return 0;
+}
+
+#if (USE_DEVICE_NODE == 0)
+static int btmtk_set_audio_slave(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u8 audio_cmd[AUDIO_SETTING_CMD_LEN] = { 0x01, 0x72, 0xFC, 0x04, 0x49, 0x00, 0x80, 0x00 };
+ u8 audio_event[AUDIO_SETTING_EVT_LEN] = { 0x04, 0x0E, 0x04, 0x01, 0x72, 0xFC, 0x00 };
+ struct fw_cfg_struct *audio_setting = &bdev->bt_cfg.audio_cmd;
+
+ BTMTK_INFO("%s enter", __func__);
+ if (audio_setting->content && audio_setting->length) {
+ BTMTK_INFO("%s load audio setting from bt.cfg", __func__);
+ memcpy((audio_cmd + 4), audio_setting->content, audio_setting->length);
+ } else {
+ BTMTK_INFO("%s load default audio cmd", __func__);
+ }
+ BTMTK_INFO_RAW(audio_cmd, AUDIO_SETTING_CMD_LEN, "%s: Send CMD:", __func__);
+ ret = btmtk_main_send_cmd(bdev, audio_cmd, AUDIO_SETTING_CMD_LEN,
+ audio_event, AUDIO_SETTING_EVT_LEN, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ BTMTK_INFO("%s exit", __func__);
+ return ret;
+}
+
+static int btmtk_read_pin_mux_setting(struct btmtk_dev *bdev, const uint8_t *cmd,
+ const int cmd_len, const uint8_t *event, const int event_len, u32 *value)
+{
+ int ret = 0;
+
+ BTMTK_INFO("%s enter", __func__);
+ ret = btmtk_main_send_cmd(bdev, cmd, cmd_len,
+ event, event_len, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ *value = (bdev->io_buf[READ_PINMUX_EVT_REAL_LEN - 1] << 24) +
+ (bdev->io_buf[READ_PINMUX_EVT_REAL_LEN - 2] << 16) +
+ (bdev->io_buf[READ_PINMUX_EVT_REAL_LEN - 3] << 8) +
+ bdev->io_buf[READ_PINMUX_EVT_REAL_LEN - 4];
+ BTMTK_INFO("%s, value=0x%08x", __func__, *value);
+ return ret;
+}
+
+static int btmtk_write_pin_mux_setting(struct btmtk_dev *bdev, uint8_t *cmd,
+ int cmd_len, const uint8_t *event, const int event_len, u32 value)
+{
+ int ret = 0;
+
+ BTMTK_INFO("%s begin, value = 0x%08x", __func__, value);
+
+ cmd[cmd_len - 4] = (value & 0x000000FF);
+ cmd[cmd_len - 3] = ((value & 0x0000FF00) >> 8);
+ cmd[cmd_len - 2] = ((value & 0x00FF0000) >> 16);
+ cmd[cmd_len - 1] = ((value & 0xFF000000) >> 24);
+
+ ret = btmtk_main_send_cmd(bdev, cmd, cmd_len,
+ event, event_len, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ BTMTK_INFO("%s exit", __func__);
+ return ret;
+}
+
+static int btmtk_set_audio_pin_mux(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ unsigned int i = 0;
+ u32 pinmux = 0;
+ u8 read_pinmux_cmd[READ_PINMUX_CMD_LEN] = { 0x01, 0xD1, 0xFC, 0x04, 0x50, 0x50, 0x00, 0x70 };
+ u8 read_pinmux_event[READ_PINMUX_EVT_CMP_LEN] = { 0x04, 0x0E, 0x08, 0x01, 0xD1, 0xFC };
+ u8 write_pinmux_cmd[WRITE_PINMUX_CMD_LEN] = { 0x01, 0xD0, 0xFC, 0x08, 0x50, 0x50, 0x00, 0x70,
+ 0x00, 0x10, 0x11, 0x01 };
+ u8 write_pinmux_event[WRITE_PINMUX_EVT_LEN] = { 0x04, 0x0E, 0x04, 0x01, 0xD0, 0xFC, 0x00 };
+
+ for (i = 0; i < PINMUX_REG_NUM; i++) {
+ pinmux = 0;
+ if (i == PINMUX_REG_NUM - 1) {
+ read_pinmux_cmd[READ_PINMUX_CMD_LEN - 4] = 0x54;
+ write_pinmux_cmd[WRITE_PINMUX_CMD_LEN - 8] = 0x54;
+ }
+ ret = btmtk_read_pin_mux_setting(bdev, read_pinmux_cmd, READ_PINMUX_CMD_LEN,
+ read_pinmux_event, READ_PINMUX_EVT_CMP_LEN, &pinmux);
+ if (ret) {
+ BTMTK_ERR("%s, btmtk_read_pin_mux_setting error(%d)", __func__, ret);
+ goto exit;
+ }
+ if (write_pinmux_cmd[WRITE_PINMUX_CMD_LEN - 8] == 0x50) {
+ pinmux &= 0x00FFFFFF;
+ pinmux |= 0x11000000;
+ } else if (write_pinmux_cmd[WRITE_PINMUX_CMD_LEN - 8] == 0x54) {
+ pinmux &= 0xFFFFF0F0;
+ pinmux |= 0x00000101;
+ } else {
+ BTMTK_ERR("%s, pinmux register is error, write_pinmux_cmd[%d] = 0x%02x",
+ __func__, WRITE_PINMUX_CMD_LEN - 8,
+ write_pinmux_cmd[WRITE_PINMUX_CMD_LEN - 8]);
+ ret = -1;
+ goto exit;
+ }
+
+ ret = btmtk_write_pin_mux_setting(bdev, write_pinmux_cmd, WRITE_PINMUX_CMD_LEN,
+ write_pinmux_event, WRITE_PINMUX_EVT_LEN, pinmux);
+
+ if (ret) {
+ BTMTK_ERR("%s, btmtk_write_pin_mux_setting error(%d)", __func__, ret);
+ goto exit;
+ }
+
+ pinmux = 0;
+ ret = btmtk_read_pin_mux_setting(bdev, read_pinmux_cmd, READ_PINMUX_CMD_LEN,
+ read_pinmux_event, READ_PINMUX_EVT_CMP_LEN, &pinmux);
+ if (ret) {
+ BTMTK_ERR("%s, btmtk_read_pin_mux_setting error(%d)", __func__, ret);
+ goto exit;
+ }
+ BTMTK_INFO("%s, confirm pinmux register 0x%02x pinmux 0x%08x", __func__,
+ write_pinmux_cmd[4], pinmux);
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_set_audio_pin_mux_7902(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ unsigned int i = 0;
+ u8 write_pinmux_cmd[WRITE_PINMUX_CMD_LEN_7902] = { 0x01, 0x34, 0xFC, 0x03, 0x02, 0x00, 0x01 };
+ u8 write_pinmux_event[WRITE_PINMUX_EVT_LEN_7902] = { 0x04, 0x0E, 0x04, 0x01, 0x34, 0xFC, 0x00 };
+ u8 write_pinmux_pin_num[PINMUX_REG_NUM_7902] = { 0x00, 0x01, 0x02, 0x04 };
+ u8 write_pinmux_pin_mode[PINMUX_REG_NUM_7902] = { 0x01, 0x01, 0x01, 0x01 };
+ struct fw_cfg_struct *audio_pinmux_num = &bdev->bt_cfg.audio_pinmux_num;
+ struct fw_cfg_struct *audio_pinmux_mode = &bdev->bt_cfg.audio_pinmux_mode;
+
+ if (audio_pinmux_num->content && audio_pinmux_num->length) {
+ BTMTK_INFO("%s load audio pinmux num from bt.cfg", __func__);
+ memcpy(write_pinmux_pin_num, audio_pinmux_num->content, audio_pinmux_num->length);
+ } else {
+ BTMTK_INFO("%s load default audio pinmux num", __func__);
+ }
+ BTMTK_INFO_RAW(write_pinmux_pin_num, PINMUX_REG_NUM_7902, "%s: Pin NUM:", __func__);
+
+ if (audio_pinmux_mode->content && audio_pinmux_mode->length) {
+ BTMTK_INFO("%s load audio pinmux mode from bt.cfg", __func__);
+ memcpy(write_pinmux_pin_mode, audio_pinmux_mode->content, audio_pinmux_mode->length);
+ } else {
+ BTMTK_INFO("%s load default audio pinmux mode", __func__);
+ }
+ BTMTK_INFO_RAW(write_pinmux_pin_mode, PINMUX_REG_NUM_7902, "%s: Pin MODE:", __func__);
+
+ for (i = 0; i < PINMUX_REG_NUM_7902; i++) {
+ write_pinmux_cmd[WRITE_PINMUX_CMD_LEN_7902 - 2] = write_pinmux_pin_num[i];
+ write_pinmux_cmd[WRITE_PINMUX_CMD_LEN_7902 - 1] = write_pinmux_pin_mode[i];
+
+ BTMTK_INFO_RAW(write_pinmux_cmd, WRITE_PINMUX_CMD_LEN_7902, "%s: Send CMD:", __func__);
+ ret = btmtk_main_send_cmd(bdev, write_pinmux_cmd, WRITE_PINMUX_CMD_LEN_7902,
+ write_pinmux_event, WRITE_PINMUX_EVT_LEN_7902, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+ goto exit;
+ }
+
+ BTMTK_INFO("%s, confirm pinmux num : 0x%02x, mode :0x%02x", __func__,
+ write_pinmux_pin_num[i], write_pinmux_pin_mode[i]);
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_set_audio_setting(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+
+ if (bdev->bt_cfg.support_audio_setting == true) {
+ ret = btmtk_set_audio_slave(bdev);
+ if (ret) {
+ BTMTK_ERR("%s, btmtk_sdio_set_audio_slave error(%d)", __func__, ret);
+ return ret;
+ }
+
+ if (is_mt7902(bdev->chip_id))
+ ret = btmtk_set_audio_pin_mux_7902(bdev);
+ else
+ ret = btmtk_set_audio_pin_mux(bdev);
+
+ if (ret) {
+ BTMTK_ERR("%s, btmtk_sdio_set_audio_pin_mux error(%d)", __func__, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+#endif // (USE_DEVICE_NODE == 0)
+
+int btmtk_recv_acl(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = NULL;
+
+ if (hdev == NULL || skb == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL || bdev->workqueue == NULL) {
+ BTMTK_ERR("%s, bdev or workqueue is invalid!", __func__);
+ return -EINVAL;
+ }
+
+ skb_queue_tail(&bdev->rx_q, skb);
+ queue_work(bdev->workqueue, &bdev->rx_work);
+
+ /* remove it, if workqueue can't be scheduled, you can reuse it */
+#if 0
+ skip_pkt = btmtk_dispatch_fwlog(bdev, skb);
+ if (skip_pkt == 0)
+ err = hci_recv_frame(hdev, skb);
+#endif
+ return 0;
+}
+
+
+int btmtk_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = NULL;
+
+ if (hdev == NULL || skb == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL || bdev->workqueue == NULL) {
+ BTMTK_ERR("%s, bdev or workqueue is invalid!", __func__);
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /* 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 (hdr->evt == 0xe4) {
+ * BTMTK_DBG("%s hdr->evt is %02x", __func__, hdr->evt);
+ * hdr->evt = HCI_EV_VENDOR;
+ * }
+ */
+
+ //BTMTK_DBG_RAW(skb->data, skb->len, "%s, recv evt(hci_recv_frame)", __func__);
+
+ skb_queue_tail(&bdev->rx_q, skb);
+ queue_work(bdev->workqueue, &bdev->rx_work);
+
+ /* remove it, if workqueue can't be scheduled, you can reuse it */
+#if 0
+ skip_pkt = btmtk_dispatch_event(hdev, skb);
+ if (skip_pkt == 0)
+ err = hci_recv_frame(hdev, skb);
+
+ if (err < 0) {
+ BTMTK_ERR("%s hci_recv_failed, err = %d", __func__, err);
+ goto err_out;
+ }
+#endif
+ return 0;
+}
+
+int btmtk_recv_iso(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = NULL;
+
+ if (hdev == NULL || skb == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL || bdev->workqueue == NULL) {
+ BTMTK_ERR("%s, bdev or workqueue is invalid!", __func__);
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ skb_queue_tail(&bdev->rx_q, skb);
+ queue_work(bdev->workqueue, &bdev->rx_work);
+
+ return 0;
+}
+
+#if (USE_DEVICE_NODE == 1)
+int btmtk_recv_rhw(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = NULL;
+
+ if (hdev == NULL || skb == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL || bdev->workqueue == NULL) {
+ BTMTK_ERR("%s, bdev or workqueue is invalid!", __func__);
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ skb_queue_tail(&bdev->rx_q, skb);
+ queue_work(bdev->workqueue, &bdev->rx_work);
+
+ return 0;
+}
+#endif
+
+int btmtk_main_send_cmd(struct btmtk_dev *bdev, const uint8_t *cmd,
+ const int cmd_len, const uint8_t *event, const int event_len, int delay,
+ int retry, int pkt_type)
+{
+ struct sk_buff *skb = NULL;
+ int ret = 0;
+ int state = BTMTK_STATE_INIT;
+
+ if (bdev == NULL || bdev->hdev == NULL ||
+ cmd == NULL || cmd_len <= 0) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (!is_mt66xx(bdev->chip_id) &&
+ memcmp(cmd, main_info.wmt_over_hci_header, WMT_OVER_HCI_HEADER_SIZE) &&
+ pkt_type != BTMTK_TX_ACL_FROM_DRV &&
+ bdev->power_state != BTMTK_DONGLE_STATE_POWER_ON) {
+ BTMTK_WARN("%s: chip power isn't on, ignore this command, state is %d",
+ __func__, bdev->power_state);
+ goto exit;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't send any cmd to FW!!!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ skb = alloc_skb(cmd_len + BT_SKB_RESERVE, GFP_ATOMIC);
+ if (skb == NULL) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ ret = -ENOMEM;
+ goto exit;
+ }
+ /* Reserv for core and drivers use */
+ skb_reserve(skb, 7);
+ bt_cb(skb)->pkt_type = HCI_COMMAND_PKT;
+ memcpy(skb->data, cmd, cmd_len);
+ skb->len = cmd_len;
+
+#if ENABLESTP
+ skb = mtk_add_stp(bdev, skb);
+#endif
+ /* wmt cmd and download fw patch using wmt cmd with USB interface, need use
+ * usb_control_msg to recv wmt event;
+ * other HIF don't use this method to recv wmt event
+ */
+
+ ret = main_info.hif_hook.send_and_recv(bdev,
+ skb,
+ event, event_len,
+ delay, retry, pkt_type);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s send_and_recv failed!!", __func__);
+ /* ERRNUM is used to handle when skb has been sent successful,
+ * but wait related event failed, in this case, we don't need to free skb here,
+ * otherwise, it will be double free.
+ */
+ if (ret != -ERRNUM) {
+ kfree_skb(skb);
+ skb = NULL;
+ }
+ }
+
+exit:
+ BTMTK_DBG("%s end!!", __func__);
+ return ret;
+}
+
+int btmtk_load_code_from_bin(u8 **image, char *bin_name, struct device *dev,
+ u32 *code_len, u8 retry)
+{
+ const struct firmware *fw_entry = NULL;
+ int err = 0;
+
+ if (!bin_name) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -1;
+ }
+ BTMTK_INFO("%s: load %s", __func__, bin_name);
+
+ do {
+ err = request_firmware(&fw_entry, bin_name, dev);
+ if (err >= 0) {
+ break;
+ } else if (retry <= 0) {
+ *image = NULL;
+ BTMTK_INFO("%s: request_firmware %d times fail, maybe file not exist, err = %d",
+ __func__, 10, err);
+ return -1;
+ }
+ BTMTK_INFO("%s: request_firmware fail, maybe file not exist, err = %d, retry = %d",
+ __func__, err, retry);
+ msleep(100);
+ } while (retry-- > 0);
+
+ *image = vmalloc(ALIGN_4(fw_entry->size));
+ if (*image == NULL) {
+ *code_len = 0;
+ BTMTK_ERR("%s: vmalloc failed!! error code = %d", __func__, err);
+ release_firmware(fw_entry);
+ return -1;
+ }
+
+ memcpy(*image, fw_entry->data, fw_entry->size);
+ *code_len = fw_entry->size;
+
+ release_firmware(fw_entry);
+ return 0;
+}
+
+static u8 *btmtk_memstr(u8 *buf, u32 buf_len, char *substr)
+{
+ u32 sublen = 0;
+ int i = 0;
+ u8 *cur = NULL;
+
+ if (buf == NULL || buf_len == 0 || substr == NULL) {
+ BTMTK_WARN("%s, parameter is invalid!", __func__);
+ return NULL;
+ }
+
+ sublen = strlen(substr);
+ cur = buf + buf_len - 1;
+ for (i = buf_len - 1; i >= 0; i--) {
+ if (buf_len - 1 - i < sublen) {
+ cur--;
+ continue;
+ }
+ if (*cur == *substr && memcmp(cur, substr, sublen) == 0)
+ return cur;
+
+ cur--;
+ }
+
+ return NULL;
+}
+
+static void btmtk_print_bt_patch_info(struct btmtk_dev *bdev, u8 *fwbuf, u32 fwbuf_len)
+{
+ struct _PATCH_HEADER *patchHdr = NULL;
+ struct _Global_Descr *globalDesrc = NULL;
+ u8 *fw_version = NULL;
+ u32 fw_version_len = 0;
+
+ if (fwbuf == NULL) {
+ BTMTK_WARN("%s, fwbuf is NULL!", __func__);
+ return;
+ }
+
+ patchHdr = (struct _PATCH_HEADER *)fwbuf;
+ fw_version = btmtk_memstr(fwbuf, fwbuf_len, FW_VERSION_KEY_WORDS);
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id) || is_mt66xx(bdev->chip_id))
+ globalDesrc = (struct _Global_Descr *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE);
+
+ BTMTK_INFO("[btmtk] =============== Patch Info ==============");
+
+ if (fw_version) {
+ memset(main_info.fw_version_str, 0, FW_VERSION_BUF_SIZE);
+ fw_version_len = MIN(((u32)(fwbuf + fwbuf_len - fw_version)), FW_VERSION_BUF_SIZE);
+ memcpy(main_info.fw_version_str, fw_version, fw_version_len);
+ BTMTK_INFO("[btmtk] fw_version = %s", main_info.fw_version_str);
+ }
+
+ if (patchHdr) {
+ BTMTK_INFO("[btmtk] Built Time = %s", patchHdr->ucDateTime);
+ BTMTK_INFO("[btmtk] Hw Ver = 0x%04x", patchHdr->u2HwVer);
+ BTMTK_INFO("[btmtk] Sw Ver = 0x%04x", patchHdr->u2SwVer);
+ BTMTK_INFO("[btmtk] Magic Number = 0x%08x", patchHdr->u4MagicNum);
+
+ BTMTK_INFO("[btmtk] Platform = %c%c%c%c",
+ patchHdr->ucPlatform[0],
+ patchHdr->ucPlatform[1],
+ patchHdr->ucPlatform[2],
+ patchHdr->ucPlatform[3]);
+ } else
+ BTMTK_WARN("%s, patchHdr is NULL!", __func__);
+
+ if (globalDesrc) {
+ BTMTK_INFO("[btmtk] Patch Ver = 0x%08x", globalDesrc->u4PatchVer);
+ BTMTK_INFO("[btmtk] Section num = 0x%08x", globalDesrc->u4SectionNum);
+ } else
+ BTMTK_WARN("%s, globalDesrc is NULL!", __func__);
+ BTMTK_INFO("[btmtk] =========================================");
+}
+
+static void btmtk_print_wifi_patch_info(struct btmtk_dev *bdev, u8 *fwbuf)
+{
+ struct _PATCH_HEADER *patchHdr = NULL;
+ struct _Global_Descr *globalDesrc = NULL;
+
+ if (fwbuf == NULL) {
+ BTMTK_WARN("%s, fwbuf is NULL!", __func__);
+ return;
+ }
+
+ patchHdr = (struct _PATCH_HEADER *)fwbuf;
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id))
+ globalDesrc = (struct _Global_Descr *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE);
+
+ BTMTK_INFO("[btmtk] =============== Wifi Patch Info ==============");
+ if (patchHdr) {
+ BTMTK_INFO("[btmtk] Built Time = %s", patchHdr->ucDateTime);
+ BTMTK_INFO("[btmtk] Hw Ver = 0x%04x",
+ ((patchHdr->u2HwVer & 0x00ff) << 8) | ((patchHdr->u2HwVer & 0xff00) >> 8));
+ BTMTK_INFO("[btmtk] Sw Ver = 0x%04x",
+ ((patchHdr->u2SwVer & 0x00ff) << 8) | ((patchHdr->u2SwVer & 0xff00) >> 8));
+ BTMTK_INFO("[btmtk] Magic Number = 0x%08x", be2cpu32(patchHdr->u4MagicNum));
+
+ BTMTK_INFO("[btmtk] Platform = %c%c%c%c",
+ patchHdr->ucPlatform[0],
+ patchHdr->ucPlatform[1],
+ patchHdr->ucPlatform[2],
+ patchHdr->ucPlatform[3]);
+ } else
+ BTMTK_WARN("%s, patchHdr is NULL!", __func__);
+
+ if (globalDesrc) {
+ BTMTK_INFO("[btmtk] Patch Ver = 0x%08x",
+ be2cpu32(globalDesrc->u4PatchVer));
+ BTMTK_INFO("[btmtk] Section num = 0x%08x",
+ be2cpu32(globalDesrc->u4SectionNum));
+ } else
+ BTMTK_WARN("%s, globalDesrc is NULL!", __func__);
+ BTMTK_INFO("[btmtk] =========================================");
+}
+
+static int btmtk_send_wmt_download_cmd(struct btmtk_dev *bdev, u8 *cmd,
+ int cmd_len, u8 *event, int event_len, struct _Section_Map *sectionMap,
+ u8 fw_state, u8 dma_flag, int patch_flag)
+{
+ int payload_len = 0;
+ int ret = -1;
+ int i = 0;
+ u32 revert_SecSpec = 0;
+
+ if (bdev == NULL || cmd == NULL || event == NULL || sectionMap == NULL) {
+ BTMTK_ERR("%s: invalid parameter!", __func__);
+ return ret;
+ }
+
+ /* need refine this cmd to mtk_wmt_hdr struct*/
+ /* prepare HCI header */
+ cmd[0] = 0x01;
+ cmd[1] = 0x6F;
+ cmd[2] = 0xFC;
+
+ /* prepare WMT header */
+ cmd[4] = 0x01;
+ cmd[5] = 0x01; /* opcode */
+
+ if (fw_state == 0) {
+ /* prepare WMT DL cmd */
+ payload_len = SEC_MAP_NEED_SEND_SIZE + 2;
+
+ cmd[3] = (payload_len + 4) & 0xFF; /* length*/
+ cmd[6] = payload_len & 0xFF;
+ cmd[7] = (payload_len >> 8) & 0xFF;
+ cmd[8] = 0x00; /* which is the FW download state 0 */
+ cmd[9] = dma_flag; /* 1:using DMA to download, 0:using legacy wmt cmd*/
+ cmd_len = SEC_MAP_NEED_SEND_SIZE + PATCH_HEADER_SIZE;
+
+ if (patch_flag == WIFI_DOWNLOAD) {
+ for (i = 0; i < SECTION_SPEC_NUM; i++) {
+ revert_SecSpec = be2cpu32(sectionMap->u4SecSpec[i]);
+ memcpy(&cmd[PATCH_HEADER_SIZE] + i * sizeof(u32), (u8 *)&revert_SecSpec, sizeof(u32));
+ }
+ } else
+ memcpy(&cmd[PATCH_HEADER_SIZE], (u8 *)(sectionMap->u4SecSpec), SEC_MAP_NEED_SEND_SIZE);
+
+ BTMTK_INFO_RAW(cmd, cmd_len, "%s: CMD: len[%d]", __func__, cmd_len);
+
+ ret = btmtk_main_send_cmd(bdev, cmd, cmd_len,
+ event, event_len, DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: send wmd dl cmd failed, terminate!", __func__);
+ return PATCH_ERR;
+ }
+
+ if (bdev->recv_evt_len >= event_len)
+ return bdev->io_buf[PATCH_STATUS];
+
+ return PATCH_ERR;
+ }
+
+ BTMTK_ERR("%s: fw state is error!", __func__);
+ return ret;
+}
+
+static int btmtk_load_fw_patch_using_wmt_cmd(struct btmtk_dev *bdev,
+ u8 *image, u8 *fwbuf, u8 *event, int event_len, u32 patch_len, int offset)
+{
+ int ret = 0;
+ u32 cur_len = 0;
+ s32 sent_len;
+ int first_block = 1;
+ u8 phase;
+ int delay = PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME;
+ int retry = PATCH_DOWNLOAD_PHASE1_2_RETRY;
+
+ if (bdev == NULL || image == NULL || fwbuf == NULL) {
+ BTMTK_WARN("%s, invalid parameters!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ /* loading rom patch */
+ while (1) {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE;
+
+ sent_len = (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+
+ if (sent_len > 0) {
+ if (first_block == 1) {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ } else if (sent_len == sent_len_max) {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ } else {
+ phase = PATCH_PHASE3;
+ }
+
+
+ /* prepare HCI header */
+ image[0] = 0x02;
+ image[1] = 0x6F;
+ image[2] = 0xFC;
+ image[3] = (sent_len + 5) & 0xFF;
+ image[4] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ image[5] = 0x01;
+ image[6] = 0x01;
+ image[7] = (sent_len + 1) & 0xFF;
+ image[8] = ((sent_len + 1) >> 8) & 0xFF;
+
+ image[9] = phase;
+ memcpy(&image[10], fwbuf + offset + cur_len, sent_len);
+ if (phase == PATCH_PHASE3) {
+ if (is_mt7922(bdev->chip_id) || is_mt7902(bdev->chip_id)) {
+ /* if secure boot enable, it need take 76ms at less
+ * for RSA check.
+ */
+ delay = PATCH_DOWNLOAD_PHASE3_SECURE_BOOT_DELAY_TIME;
+ retry = PATCH_DOWNLOAD_PHASE3_RETRY;
+ } else {
+ delay = PATCH_DOWNLOAD_PHASE3_DELAY_TIME;
+ retry = PATCH_DOWNLOAD_PHASE3_RETRY;
+ }
+ }
+
+ cur_len += sent_len;
+ BTMTK_DBG("%s: sent_len = %d, cur_len = %d, phase = %d", __func__,
+ sent_len, cur_len, phase);
+
+ ret = btmtk_main_send_cmd(bdev, image, sent_len + PATCH_HEADER_SIZE,
+ event, event_len, delay, retry, BTMTK_TX_ACL_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_INFO("%s: send patch failed, terminate", __func__);
+ goto exit;
+ }
+ } else
+ break;
+ }
+
+exit:
+ return ret;
+}
+
+void btmtk_send_hw_err_to_host(struct btmtk_dev *bdev)
+{
+ struct sk_buff *skb = NULL;
+ u8 hwerr_event[HWERR_EVT_LEN] = { 0x04, 0x10, 0x01, 0xff };
+
+ BTMTK_ERR("%s reset_stack_flag = %d!!", __func__, main_info.reset_stack_flag);
+ if (main_info.reset_stack_flag) {
+ skb = alloc_skb(HWERR_EVT_LEN + BT_SKB_RESERVE, GFP_KERNEL);
+ if (skb == NULL) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ } else {
+#if (USE_DEVICE_NODE == 0)
+ hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
+ skb->data[0] = hwerr_event[1];
+ skb->data[1] = hwerr_event[2];
+ skb->data[2] = main_info.reset_stack_flag;
+ skb->len = HWERR_EVT_LEN - 1;
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s: hw err event:", __func__);
+ hci_recv_frame(bdev->hdev, skb);
+#else
+ /* send to RX buffer instead of hci driver */
+ skb_reserve(skb, BT_SKB_RESERVE);
+ hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
+ skb->data[0] = hwerr_event[1];
+ skb->data[1] = hwerr_event[2];
+ skb->data[2] = 0x00;
+ skb->len = HWERR_EVT_LEN - 1;
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s: hw err event: ", __func__);
+ skb_queue_tail(&bdev->rx_q, skb);
+ queue_work(bdev->workqueue, &bdev->rx_work);
+#endif
+ }
+ }
+}
+
+static int btmtk_parsing_fw_rom_patch(struct btmtk_dev *bdev,
+ u8 *fwbuf, int patch_flag)
+{
+ int loop_count = 0;
+ int ret = 0;
+ u32 section_num = 0;
+ u32 section_offset = 0;
+ u32 dl_size = 0;
+ u32 bin_type = 0;
+ u8 bin_index = 0;
+ u8 dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ struct _Section_Map *sectionMap;
+ struct _Global_Descr *globalDescr;
+
+ if (fwbuf == NULL) {
+ BTMTK_WARN("%s, fwbuf is NULL!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ globalDescr = (struct _Global_Descr *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE);
+
+ if (patch_flag == WIFI_DOWNLOAD)
+ section_num = be2cpu32(globalDescr->u4SectionNum);
+ else
+ section_num = globalDescr->u4SectionNum;
+
+ if (section_num > SECTION_NUM_MAX) {
+ BTMTK_ERR("%s: section_num 0x%08x is an error value", __func__, section_num);
+ ret = -1;
+ goto exit;
+ }
+
+ if (bdev->sectionMap_table) {
+ kfree(bdev->sectionMap_table);
+ bdev->sectionMap_table = NULL;
+ }
+
+ bdev->sectionMap_table = kmalloc_array(section_num,
+ sizeof(struct _Section_Map), GFP_ATOMIC);
+
+ do {
+ sectionMap = (struct _Section_Map *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE +
+ FW_ROM_PATCH_GD_SIZE + FW_ROM_PATCH_SEC_MAP_SIZE * loop_count);
+
+ if (patch_flag == WIFI_DOWNLOAD) {
+ /* wifi is big-endian */
+ section_offset = be2cpu32(sectionMap->u4SecOffset);
+ dl_size = be2cpu32(sectionMap->bin_info_spec.u4DLSize);
+ if (main_info.hif_hook.dl_dma)
+ dma_flag = be2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) & 0xFF;
+ bin_index = (be2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) >> 16) & 0xFF;
+ bin_type = be2cpu32(sectionMap->bin_info_spec.u4SecType);
+ } else {
+ /* BT is little-endian */
+ section_offset = sectionMap->u4SecOffset;
+ dl_size = sectionMap->bin_info_spec.u4DLSize;
+ /*
+ * loop_count = 0: BGF patch
+ * 1: BT ILM
+ * only BT ILM support DL DMA for Buzzard
+ */
+ if (main_info.hif_hook.dl_dma)
+ dma_flag = le2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) & 0xFF;
+ bin_index = (le2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) >> 16) & 0xFF;
+ bin_type = sectionMap->bin_info_spec.u4SecType;
+ }
+ BTMTK_INFO("%s: loop_count = %d, section_offset = 0x%08x, download patch_len = 0x%08x, "
+ "dl mode = %d, section bin_type = 0x%08x, bin_index =%d\n",
+ __func__, loop_count, section_offset, dl_size, dma_flag, bin_type, bin_index);
+ memcpy(&bdev->sectionMap_table[loop_count], sectionMap, sizeof(struct _Section_Map));
+ } while (++loop_count < section_num);
+
+exit:
+ return ret;
+
+}
+
+int btmtk_load_fw_by_bin_info(struct btmtk_dev *bdev,
+ u8 *fwbuf, u32 binInfo, int mode)
+{
+ u8 *pos;
+ int loop_count = 0;
+ int ret = 0;
+ u32 section_num = 0;
+ u32 section_offset = 0;
+ u32 dl_size = 0;
+ u32 temp_type = 0;
+ u8 temp_index = 0;
+ int patch_status = 0;
+ int retry = 20;
+ u8 dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ struct _Section_Map *sectionMap;
+ struct _Global_Descr *globalDescr;
+ u8 event[LD_PATCH_EVT_LEN] = {0x04, 0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[7] is status*/
+
+ /*
+ * flag 0 for bin_index u8
+ * flag 1 for bin_type u32
+ */
+ BTMTK_INFO("%s enter : mode %d expected bin_info = 0x%08x", __func__, mode, binInfo);
+
+ if (fwbuf == NULL) {
+ BTMTK_WARN("%s, fwbuf is NULL!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ globalDescr = (struct _Global_Descr *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE);
+ section_num = globalDescr->u4SectionNum;
+
+ if (section_num > SECTION_NUM_MAX) {
+ BTMTK_ERR("%s: section_num 0x%08x is an error value", __func__, section_num);
+ ret = -1;
+ goto exit;
+ }
+
+ do {
+ sectionMap = &bdev->sectionMap_table[loop_count];
+ temp_index = (le2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) >> 16) & 0xFF;
+ temp_type = le2cpu32(sectionMap->bin_info_spec.u4SecType);
+ if (mode == DOWNLOAD_BY_TYPE) {
+ if (temp_type == binInfo) {
+ BTMTK_INFO("%s, download by bin type", __func__);
+ break;
+ }
+ } else {
+ if (temp_index == (binInfo & 0xFF)) {
+ BTMTK_INFO("%s, download by bin index", __func__);
+ break;
+ }
+ }
+ } while (++loop_count < section_num);
+
+ if (loop_count == section_num) {
+ BTMTK_ERR("%s: bin_info 0x%08x is not found", __func__, binInfo);
+ ret = 0;
+ goto exit;
+ }
+
+ pos = kmalloc(UPLOAD_PATCH_UNIT, GFP_ATOMIC);
+ if (!pos) {
+ BTMTK_ERR("%s: alloc memory failed", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ /* BT is little-endian */
+ section_offset = sectionMap->u4SecOffset;
+ dl_size = sectionMap->bin_info_spec.u4DLSize;
+ /*
+ * loop_count = 0: BGF patch
+ * 1: BT ILM
+ * only BT ILM support DL DMA for Buzzard
+ */
+ if (main_info.hif_hook.dl_dma)
+ dma_flag = le2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) & 0xFF;
+
+ if (mode == DOWNLOAD_BY_INDEX) {
+ /* daynamic download need downloaded by wmt cmd */
+ dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ }
+
+ BTMTK_INFO("%s: section_count = %d, section bin_type = 0x%08x, bin_index = %d\n",
+ __func__, loop_count, temp_type, temp_index);
+
+ if (dl_size > 0) {
+ retry = 20;
+ do {
+ patch_status = btmtk_send_wmt_download_cmd(bdev, pos, 0,
+ event, LD_PATCH_EVT_LEN - 1, sectionMap, 0, dma_flag, BT_DOWNLOAD);
+ BTMTK_INFO("%s: patch_status %d", __func__, patch_status);
+
+ if (patch_status > PATCH_READY || patch_status == PATCH_ERR) {
+ BTMTK_ERR("%s: patch_status error", __func__);
+ ret = -1;
+ goto err;
+ } else if (patch_status == PATCH_READY) {
+ BTMTK_INFO("%s: no need to load rom patch section%d", __func__, loop_count);
+ goto err;
+ } else if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ msleep(100);
+ retry--;
+ } else if (patch_status == PATCH_NEED_DOWNLOAD) {
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ BTMTK_WARN("%s: Hold by another fun more than 2 seconds", __func__);
+ ret = -1;
+ goto err;
+ }
+
+ if (dma_flag == PATCH_DOWNLOAD_USING_DMA && main_info.hif_hook.dl_dma) {
+ BTMTK_INFO("%s: btmtk_load_fw_patch_using_dma!", __func__);
+ /* using DMA to download fw patch*/
+ ret = main_info.hif_hook.dl_dma(bdev,
+ pos, fwbuf,
+ dl_size, section_offset);
+ if (ret < 0) {
+ BTMTK_ERR("%s: btmtk_load_fw_patch_using_dma failed!", __func__);
+ goto err;
+ }
+ } else {
+ BTMTK_INFO("%s: btmtk_load_fw_patch_using_wmt_cmd!", __func__);
+ /* using legacy wmt cmd to download fw patch */
+ ret = btmtk_load_fw_patch_using_wmt_cmd(bdev, pos, fwbuf, event,
+ LD_PATCH_EVT_LEN - 1, dl_size, section_offset);
+ if (ret < 0) {
+ BTMTK_ERR("%s: btmtk_load_fw_patch_using_wmt_cmd failed!", __func__);
+ goto err;
+ }
+ }
+ BTMTK_INFO("%s end", __func__);
+ }
+
+err:
+ kfree(pos);
+ pos = NULL;
+
+exit:
+ return ret;
+}
+
+static int btmtk_send_fw_rom_patch_79xx(struct btmtk_dev *bdev,
+ u8 *fwbuf, int patch_flag)
+{
+ u8 *pos;
+ int loop_count = 0;
+ int ret = 0;
+ u32 section_num = 0;
+ u32 section_offset = 0;
+ u32 dl_size = 0;
+ int patch_status = 0;
+ int retry = 20;
+ u8 dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ struct _Section_Map *sectionMap;
+ struct _Global_Descr *globalDescr;
+ u8 event[LD_PATCH_EVT_LEN] = {0x04, 0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[7] is status*/
+#if DEBUG_LD_PATCH_TIME
+ struct timeval tv_start, tv_bgf, tv_ilm;
+ u32 dlt_dma = 0, dlt_all = 0;
+
+ memset(&tv_start, 0, sizeof(tv_start));
+ memset(&tv_bgf, 0, sizeof(tv_bgf));
+ memset(&tv_ilm, 0, sizeof(tv_ilm));
+// btmtk_do_gettimeofday(&tv_start);
+#endif
+ if (fwbuf == NULL) {
+ BTMTK_WARN("%s, fwbuf is NULL!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ globalDescr = (struct _Global_Descr *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE);
+
+ BTMTK_INFO("%s: loading rom patch...\n", __func__);
+
+ if (patch_flag == WIFI_DOWNLOAD)
+ section_num = be2cpu32(globalDescr->u4SectionNum);
+ else
+ section_num = globalDescr->u4SectionNum;
+ BTMTK_INFO("%s: section_num = 0x%08x\n", __func__, section_num);
+
+ if (section_num > SECTION_NUM_MAX) {
+ BTMTK_ERR("%s: section_num 0x%08x is an error value", __func__, section_num);
+ ret = -1;
+ goto exit;
+ }
+
+ pos = kmalloc(UPLOAD_PATCH_UNIT, GFP_ATOMIC);
+ if (!pos) {
+ BTMTK_ERR("%s: alloc memory failed", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ do {
+ sectionMap = (struct _Section_Map *)(fwbuf + FW_ROM_PATCH_HEADER_SIZE +
+ FW_ROM_PATCH_GD_SIZE + FW_ROM_PATCH_SEC_MAP_SIZE * loop_count);
+
+ if (patch_flag == WIFI_DOWNLOAD) {
+ /* wifi is big-endian */
+ section_offset = be2cpu32(sectionMap->u4SecOffset);
+ dl_size = be2cpu32(sectionMap->bin_info_spec.u4DLSize);
+ if (main_info.hif_hook.dl_dma)
+ dma_flag = be2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) & 0xFF;
+ } else {
+ /* BT & ZB are little-endian */
+ section_offset = sectionMap->u4SecOffset;
+ dl_size = sectionMap->bin_info_spec.u4DLSize;
+ /*
+ * loop_count = 0: BGF patch
+ * 1: BT ILM
+ * only BT ILM support DL DMA for Buzzard
+ */
+ if (main_info.hif_hook.dl_dma)
+ dma_flag = le2cpu32(sectionMap->bin_info_spec.u4DLModeCrcType) & 0xFF;
+ }
+ BTMTK_INFO("%s: loop_count = %d, section_offset = 0x%08x, download patch_len = 0x%08x, dl mode = %d\n",
+ __func__, loop_count, section_offset, dl_size, dma_flag);
+ if (dl_size > 0) {
+ retry = 20;
+ do {
+ patch_status = btmtk_send_wmt_download_cmd(bdev, pos, 0,
+ event, LD_PATCH_EVT_LEN - 1, sectionMap, 0, dma_flag, patch_flag);
+ BTMTK_INFO("%s: patch_status %d", __func__, patch_status);
+
+ if (patch_status > PATCH_READY || patch_status == PATCH_ERR) {
+ BTMTK_ERR("%s: patch_status error", __func__);
+ ret = -1;
+ goto err;
+ } else if (patch_status == PATCH_READY) {
+ BTMTK_INFO("%s: no need to load rom patch section%d", __func__, loop_count);
+ goto next_section;
+ } else if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ msleep(100);
+ retry--;
+ } else if (patch_status == PATCH_NEED_DOWNLOAD) {
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ BTMTK_WARN("%s: Hold by another fun more than 2 seconds", __func__);
+ ret = -1;
+ goto err;
+ }
+
+ if (dma_flag == PATCH_DOWNLOAD_USING_DMA && main_info.hif_hook.dl_dma) {
+ /* using DMA to download fw patch*/
+ ret = main_info.hif_hook.dl_dma(bdev,
+ pos, fwbuf,
+ dl_size, section_offset);
+ if (ret < 0) {
+ BTMTK_ERR("%s: btmtk_load_fw_patch_using_dma failed!", __func__);
+ goto err;
+ }
+ } else {
+ /* using legacy wmt cmd to download fw patch */
+ ret = btmtk_load_fw_patch_using_wmt_cmd(bdev, pos, fwbuf, event,
+ LD_PATCH_EVT_LEN - 1, dl_size, section_offset);
+ if (ret < 0) {
+ BTMTK_ERR("%s: btmtk_load_fw_patch_using_wmt_cmd failed!", __func__);
+ goto err;
+ }
+ }
+ }
+ /* FW Download finished */
+ /* remove it, comment from fw dl owner
+ * if (patch_flag == WIFI_DOWNLOAD) {
+ * if (loop_count == section_num - 1) {
+ * mdelay(500);
+ * }
+ * }
+ */
+#if DEBUG_LD_PATCH_TIME
+ if (loop_count == 0) {
+// btmtk_do_gettimeofday(&tv_bgf);
+ } else if (loop_count == 1) {
+// btmtk_do_gettimeofday(&tv_ilm);
+ if (tv_bgf.tv_sec != 0 || tv_bgf.tv_usec != 0) {
+ if (tv_ilm.tv_sec >= tv_bgf.tv_sec)
+ dlt_dma = (tv_ilm.tv_sec - tv_bgf.tv_sec) * 1000;
+ else
+ dlt_dma = (~(tv_bgf.tv_sec - tv_ilm.tv_sec) + 1) * 1000;
+
+ dlt_dma += (tv_ilm.tv_usec - tv_bgf.tv_usec) / 1000;
+ }
+ if (tv_ilm.tv_sec >= tv_start.tv_sec)
+ dlt_all = (tv_ilm.tv_sec - tv_start.tv_sec) * 1000;
+ else
+ dlt_all = (~(tv_start.tv_sec - tv_ilm.tv_sec) + 1) * 1000;
+
+ dlt_all += (tv_ilm.tv_usec - tv_start.tv_usec) / 1000;
+
+ BTMTK_INFO("LD PATCH 1 tv_start: tv_sec:%zu, tv_usec:%zu.",
+ tv_start.tv_sec, tv_start.tv_usec);
+ BTMTK_INFO("LD PATCH 2 tv_bgf: tv_sec:%zu, tv_usec:%zu.",
+ tv_bgf.tv_sec, tv_bgf.tv_usec);
+ BTMTK_INFO("LD PATCH 3 tv_ilm: tv_sec:%zu, tv_usec:%zu.",
+ tv_ilm.tv_sec, tv_ilm.tv_usec);
+
+ if (dlt_dma != 0)
+ BTMTK_INFO("LD PATCH time: ILM_DMA:%ums, ALL:%ums.",
+ dlt_dma, dlt_all);
+ else
+ BTMTK_INFO("LD PATCH time: ILM_DMA:%ums.",
+ dlt_all);
+ }
+#endif
+next_section:
+ continue;
+ } while (++loop_count < section_num);
+
+err:
+ kfree(pos);
+ pos = NULL;
+
+exit:
+ return ret;
+}
+
+int btmtk_dynamic_load_rom_patch(struct btmtk_dev *bdev, u32 binInfo)
+{
+ int ret = 0;
+ u8 *rom_patch = NULL;
+ unsigned int rom_patch_len = 0;
+
+ if (!bdev) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ /* should reload bt fw bin name , may be recover by co-dl wifi*/
+#if 0
+ if (bdev->flavor) {
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "BT_RAM_CODE_MT%04x_1a_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ } else {
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ }
+#endif
+ if (is_mt6639(bdev->chip_id) || is_mt66xx(bdev->chip_id))
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+#if (USE_DEVICE_NODE == 0)
+ "BT_RAM_CODE_MT6639_2_1_hdr.bin");
+#else
+ "BT_RAM_CODE_MT6639_1_1_hdr.bin");
+ //"BT_RAM_CODE_MT6639_1_1_nonenc_hdr.bin");
+#endif
+
+ BTMTK_INFO("%s: rom patch file name is %s", __func__,
+ bdev->rom_patch_bin_file_name);
+
+ btmtk_load_code_from_bin(&rom_patch, bdev->rom_patch_bin_file_name, NULL,
+ &rom_patch_len, 10);
+
+ if (!rom_patch) {
+ BTMTK_ERR("%s: please assign a rom patch(/etc/firmware/%s)or(/lib/firmware/%s)",
+ __func__, bdev->rom_patch_bin_file_name, bdev->rom_patch_bin_file_name);
+ ret = -1;
+ goto err;
+ }
+
+ /* dynamic download should download section by bin index */
+ ret = btmtk_load_fw_by_bin_info(bdev, rom_patch, binInfo, DOWNLOAD_BY_INDEX);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_load_rom_patch_connac3 failed!, bin type is 0x%08x",
+ __func__, binInfo);
+ ret = -1;
+ }
+
+err:
+ if (rom_patch)
+ vfree(rom_patch);
+ return ret;
+}
+
+int btmtk_load_rom_patch_connac3(struct btmtk_dev *bdev, int patch_flag)
+{
+ int ret = 0;
+ u8 *rom_patch = NULL;
+ unsigned int rom_patch_len = 0;
+ int i = 0;
+ u32 bt_bin_type[BT_BIN_TYP_NUM] = {0x00000000, 0x00000002, 0x00000003, 0x00000080,
+ 0x00000090, 0x00010000};
+ //u32 wf_bin_type[] = {0x00000100};
+
+ BTMTK_INFO("%s, patch_flag = %d!", __func__, patch_flag);
+
+ if (!bdev) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -EINVAL;
+ }
+
+ if (patch_flag == WIFI_DOWNLOAD) {
+ /* For 7902, we can't read flavor from controller successfully */
+ if (bdev->flavor)
+ /* if flavor equals 1, it represent 7920, else it represent 7921 */
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "WIFI_MT%04x_patch_mcu_1a_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ else
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "WIFI_MT%04x_patch_mcu_1_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ } else if (patch_flag == ZB_DOWNLOAD) {
+ if (bdev->flavor)
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "ZB_RAM_CODE_MT%04x_1a_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ else
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "ZB_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ } else if (patch_flag == BT_DOWNLOAD) {
+ BTMTK_INFO("%s: BT_DOWNLOAD %u", __func__, bdev->chip_id);
+ if (is_mt6639(bdev->chip_id) || is_mt66xx(bdev->chip_id))
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+#if (USE_DEVICE_NODE == 0)
+ "BT_RAM_CODE_MT6639_2_1_hdr.bin");
+#else
+ "BT_RAM_CODE_MT6639_1_1_hdr.bin");
+ //"BT_RAM_CODE_MT6639_1_1_nonenc_hdr.bin");
+#endif
+
+ } else
+ BTMTK_ERR("%s: unknow patch_flag", __func__);
+
+ BTMTK_INFO("%s: rom patch file name is %s, bt_cfg_file_name is %s", __func__,
+ bdev->rom_patch_bin_file_name, bdev->bt_cfg_file_name);
+
+ btmtk_load_code_from_bin(&rom_patch, bdev->rom_patch_bin_file_name, NULL,
+ &rom_patch_len, 10);
+
+ if (!rom_patch) {
+ BTMTK_ERR("%s: please assign a rom patch(/etc/firmware/%s)or(/lib/firmware/%s)",
+ __func__, bdev->rom_patch_bin_file_name, bdev->rom_patch_bin_file_name);
+ ret = -1;
+ goto err;
+ }
+
+ if (patch_flag == WIFI_DOWNLOAD) {
+ /*Display rom patch info*/
+ btmtk_print_wifi_patch_info(bdev, rom_patch);
+ ret = btmtk_send_fw_rom_patch_79xx(bdev, rom_patch, patch_flag);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_fw_rom_patch_79xx failed!", __func__);
+ goto err;
+ }
+ } else if (patch_flag == ZB_DOWNLOAD) {
+ /* ZB header is the same to ZB little endian */
+ btmtk_print_bt_patch_info(bdev, rom_patch, rom_patch_len);
+ ret = btmtk_send_fw_rom_patch_79xx(bdev, rom_patch, patch_flag);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_fw_rom_patch_79xx failed!", __func__);
+ goto err;
+ }
+ } else {
+ btmtk_print_bt_patch_info(bdev, rom_patch, rom_patch_len);
+ btmtk_parsing_fw_rom_patch(bdev, rom_patch, patch_flag);
+
+ for (i = 0; i < BT_BIN_TYP_NUM; i++) {
+ ret = btmtk_load_fw_by_bin_info(bdev, rom_patch, bt_bin_type[i],
+ DOWNLOAD_BY_TYPE);
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!, bin type is 0x%08x",
+ __func__, bt_bin_type[i]);
+ goto err;
+ }
+#if (USE_DEVICE_NODE == 1)
+ /* Send efem command before bt cal */
+ if (bt_bin_type[i] == 0x00000003) {
+ ret = btmtk_send_connfem_cmd(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s send connfem fail", __func__);
+ goto err;
+ }
+ }
+#endif
+ }
+ }
+
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_OFF;
+ BTMTK_INFO("%s end", __func__);
+
+err:
+ if (rom_patch)
+ vfree(rom_patch);
+ return ret;
+}
+
+/* need to remove after modify to using function pointer*/
+__weak int32_t bgfsys_bt_patch_dl(void)
+{
+ BTMTK_ERR("No bgfsys_bt_patch_dl function");
+ return -1;
+}
+
+/* need to remove after modify to using function pointer*/
+__weak int32_t btmtk_set_sleep(struct hci_dev *hdev, u_int8_t need_wait)
+{
+ //BTMTK_ERR("No btmtk_set_sleep function");
+ return -1;
+}
+
+int btmtk_load_rom_patch_66xx(struct btmtk_dev *bdev)
+{
+ return bgfsys_bt_patch_dl();
+}
+
+int btmtk_load_rom_patch(struct btmtk_dev *bdev)
+{
+ int err = -1;
+
+ if (!bdev || !bdev->hdev) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ return err;
+ }
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id)) {
+ err = btmtk_load_rom_patch_connac3(bdev, BT_DOWNLOAD);
+ if (err < 0) {
+ BTMTK_ERR("%s: btmtk_load_rom_patch bt patch failed!", __func__);
+ return err;
+ }
+
+#if CFG_SUPPORT_BT_DL_ZB_PATCH
+ if (is_mt7902(bdev->chip_id)) {
+ err = btmtk_load_rom_patch_connac3(bdev, ZB_DOWNLOAD);
+ if (err < 0) {
+ BTMTK_WARN("%s: btmtk_load_rom_patch ZB patch failed!", __func__);
+ err = 0;
+ }
+ }
+#endif
+#if CFG_SUPPORT_BT_DL_WIFI_PATCH
+ err = btmtk_load_rom_patch_connac3(bdev, WIFI_DOWNLOAD);
+ if (err < 0) {
+ BTMTK_WARN("%s: btmtk_load_rom_patch wifi patch failed!", __func__);
+ err = 0;
+ }
+#endif
+
+ } else if (is_mt66xx(bdev->chip_id)) {
+ err = btmtk_load_rom_patch_connac3(bdev, BT_DOWNLOAD);
+ } else
+ BTMTK_WARN("%s: unknown chip id (%d)", __func__, bdev->chip_id);
+ BTMTK_DBG("%s: end, err[%d]", __func__, err);
+
+ return err;
+}
+
+struct btmtk_dev *btmtk_get_dev(void)
+{
+ int i = 0;
+ struct btmtk_dev *tmp_bdev = NULL;
+
+ for (i = 0; i < btmtk_intf_num; i++) {
+ /* Find empty slot for newly probe interface.
+ * Judged from load_rom_patch is done and
+ * Identified chip_id from cap_init.
+ */
+ if (g_bdev[i]->hdev == NULL) {
+ if (i == 0)
+ g_bdev[i]->dongle_index = i;
+ else
+ g_bdev[i]->dongle_index = g_bdev[i - 1]->dongle_index + 1;
+
+ /* reset pin initial value need to be -1, used to judge after
+ * disconnected before probe, can't do chip reset
+ */
+ g_bdev[i]->bt_cfg.dongle_reset_gpio_pin = -1;
+ tmp_bdev = g_bdev[i];
+
+ /* Hook pre-defined table on state machine */
+ g_bdev[i]->cif_state = (struct btmtk_cif_state *)g_cif_state;
+ break;
+ }
+ }
+ BTMTK_INFO("%s use g_bdev[%d]", __func__, i);
+
+ return tmp_bdev;
+}
+
+void btmtk_release_dev(struct btmtk_dev *bdev)
+{
+ int i = 0;
+ struct btmtk_dev *tmp_bdev = NULL;
+
+ BTMTK_INFO("%s", __func__);
+
+ tmp_bdev = bdev;
+ if (tmp_bdev != NULL) {
+ for (i = 0; i < btmtk_intf_num; i++) {
+ /* Find slot on probed interface.
+ * Judged from load_rom_patch is done and
+ * Identified chip_id from cap_init.
+ */
+ if (memcmp(tmp_bdev, g_bdev[i], sizeof(*tmp_bdev)) == 0) {
+ memset(tmp_bdev, 0, sizeof(*tmp_bdev));
+ /* reset pin initial value need to be -1, used to judge after
+ * disconnected before probe, can't do chip reset
+ */
+ bdev->bt_cfg.dongle_reset_gpio_pin = -1;
+
+ tmp_bdev = NULL;
+ break;
+ }
+ }
+ }
+
+}
+
+struct btmtk_dev *btmtk_allocate_dev_memory(struct device *dev)
+{
+ struct btmtk_dev *bdev;
+ size_t len = sizeof(*bdev);
+
+ BTMTK_INFO("%s", __func__);
+
+ if (dev != NULL)
+ bdev = devm_kzalloc(dev, len, GFP_KERNEL);
+ else
+ bdev = kzalloc(len, GFP_KERNEL);
+
+ if (!bdev)
+ return NULL;
+
+ btmtk_set_chip_state(bdev, BTMTK_STATE_INIT);
+
+ return bdev;
+}
+
+void btmtk_free_dev_memory(struct device *dev, struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s", __func__);
+
+ if (bdev != NULL) {
+ if (dev != NULL)
+ devm_kfree(dev, bdev);
+ else
+ kfree(bdev);
+ }
+}
+
+static int btmtk_calibration_flow(struct btmtk_dev *bdev)
+{
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is NULL !", __func__);
+ return -1;
+ }
+
+ btmtk_cif_send_calibration(bdev);
+ BTMTK_INFO("%s done", __func__);
+ return 0;
+}
+
+int btmtk_send_wmt_power_on_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[WMT_POWER_ON_CMD_LEN] = { 0x01, 0x6F, 0xFC, 0x06, 0x01, 0x06, 0x02, 0x00, 0x00, 0x01 };
+ u8 event[WMT_POWER_ON_EVT_HDR_LEN] = { 0x04, 0xE4, 0x05, 0x02, 0x06, 0x01, 0x00 }; /* event[7] is key */
+ int ret = -1, retry = RETRY_TIMES;
+
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is NULL !", __func__);
+ return ret;
+ }
+
+retry_again:
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, WMT_POWER_ON_CMD_LEN,
+ event, WMT_POWER_ON_EVT_HDR_LEN,
+ WMT_DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+ bdev->power_state = BTMTK_DONGLE_STATE_ERROR;
+ ret = -1;
+ } else if (ret == 0 && bdev->recv_evt_len > 0) {
+ switch (bdev->io_buf[WMT_POWER_ON_EVT_RESULT_OFFSET]) {
+ case 0: /* successful */
+ BTMTK_INFO("%s: OK", __func__);
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_ON;
+ break;
+ case 2: /* TODO:retry */
+ if (retry > 0) {
+ /* comment from fw, we need to retry a sec until power on sucessfully. */
+ retry--;
+ BTMTK_INFO("%s: need to try again", __func__);
+ msleep(50);
+ goto retry_again;
+ }
+ break;
+ default:
+ BTMTK_WARN("%s: Unknown result: %02X", __func__, bdev->io_buf[WMT_POWER_ON_EVT_RESULT_OFFSET]);
+ bdev->power_state = BTMTK_DONGLE_STATE_ERROR;
+ ret = -1;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int btmtk_send_wmt_power_off_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[WMT_POWER_OFF_CMD_LEN] = { 0x01, 0x6F, 0xFC, 0x06, 0x01, 0x06, 0x02, 0x00, 0x00, 0x00 };
+ /* To-Do, for event check */
+ u8 event[WMT_POWER_OFF_EVT_HDR_LEN] = { 0x04, 0xE4, 0x05, 0x02, 0x06, 0x01, 0x00 };
+ int ret = -1;
+
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is NULL !", __func__);
+ return ret;
+ }
+
+ if (bdev->power_state == BTMTK_DONGLE_STATE_POWER_OFF) {
+ BTMTK_WARN("%s: power_state already power off", __func__);
+ return 0;
+ }
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, WMT_POWER_OFF_CMD_LEN,
+ event, WMT_POWER_OFF_EVT_HDR_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+ bdev->power_state = BTMTK_DONGLE_STATE_ERROR;
+ return ret;
+ }
+
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_OFF;
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+/* Check power status, if power is off, try to set power on */
+int btmtk_reset_power_on(struct btmtk_dev *bdev)
+{
+ if (bdev->power_state == BTMTK_DONGLE_STATE_POWER_OFF) {
+ bdev->power_state = BTMTK_DONGLE_STATE_ERROR;
+ if (btmtk_send_wmt_power_on_cmd(bdev) < 0)
+ return -1;
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_ON;
+ }
+
+ if (bdev->power_state != BTMTK_DONGLE_STATE_POWER_ON) {
+ BTMTK_WARN("%s: end of Incorrect state:%d", __func__, bdev->power_state);
+ return -1;
+ }
+ BTMTK_INFO("%s: end success", __func__);
+
+ return 0;
+}
+
+int btmtk_picus_enable(struct btmtk_dev *bdev)
+{
+ u8 dft_enable_cmd[PICUS_ENABLE_CMD_LEN] = { 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02 };
+ u8 *enable_cmd = NULL;
+ u8 enable_event[PICUS_ENABLE_EVT_HDR_LEN] = { 0x04, 0x0E, 0x08, 0x01, 0x5D, 0xFC, 0x00, 0x00, 0x00 };
+ int enable_len = 0;
+ int ret = -1; /* if successful, 0 */
+
+ struct fw_cfg_struct *picus_setting = &bdev->bt_cfg.picus_enable;
+
+ BTMTK_INFO("%s", __func__);
+
+ if (picus_setting->content && picus_setting->length) {
+ BTMTK_INFO("%s load picus from bt.cfg", __func__);
+ enable_cmd = picus_setting->content;
+ enable_len = picus_setting->length;
+ } else {
+ enable_cmd = dft_enable_cmd;
+ enable_len = PICUS_ENABLE_CMD_LEN;
+ }
+ BTMTK_INFO_RAW(enable_cmd, enable_len, "%s: Send CMD:", __func__);
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id))
+ ret = btmtk_main_send_cmd(bdev,
+ enable_cmd, enable_len,
+ enable_event, PICUS_ENABLE_EVT_HDR_LEN,
+ DELAY_TIMES, RETRY_TIMES,
+ BTMTK_TX_PKT_FROM_HOST);
+ else
+ BTMTK_WARN("%s: not support for 0x%x", __func__, bdev->chip_id);
+
+ BTMTK_INFO("%s: ret %d", __func__, ret);
+ return ret;
+}
+
+int btmtk_picus_disable(struct btmtk_dev *bdev)
+{
+ u8 dft_disable_cmd[PICUS_DISABLE_CMD_LEN] = { 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x00 };
+ u8 dft_disable_event[PICUS_DISABLE_EVT_HDR_LEN] = { 0x04, 0x0E, 0x08, 0x01, 0x5D, 0xFC, 0x00, 0x00, 0x00 };
+ int ret = -1; /* if successful, 0 */
+
+ BTMTK_INFO("%s\n", __func__);
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id))
+ ret = btmtk_main_send_cmd(bdev,
+ dft_disable_cmd, PICUS_DISABLE_CMD_LEN,
+ dft_disable_event, PICUS_DISABLE_EVT_HDR_LEN,
+ DELAY_TIMES, RETRY_TIMES,
+ BTMTK_TX_PKT_FROM_HOST);
+ else
+ BTMTK_WARN("%s: not support for 0x%x", __func__, bdev->chip_id);
+
+ BTMTK_INFO("%s: ret %d", __func__, ret);
+ return ret;
+}
+
+int btmtk_load_fw_cfg_setting(char *block_name, struct fw_cfg_struct *save_content,
+ int counter, u8 *searchcontent, enum fw_cfg_index_len index_length)
+{
+ int ret = 0, i = 0;
+ u16 temp_len = 0;
+ u8 temp[TEMP_LEN]; /* save for total hex number */
+ unsigned long parsing_result = 0;
+ char *search_result = NULL, *ptr = NULL;
+ char *search_end = NULL;
+ char search[SEARCH_LEN];
+ char *next_block = NULL;
+ char number[CHAR2HEX_SIZE + 1]; /* 1 is for '\0' */
+
+ memset(search, 0, SEARCH_LEN);
+ memset(temp, 0, TEMP_LEN);
+ memset(number, 0, CHAR2HEX_SIZE + 1);
+
+ if (searchcontent == NULL) {
+ BTMTK_ERR("%s: Searchcontent is NULL", __func__);
+ return -1;
+ }
+
+ /* search block name */
+ for (i = 0; i < counter; i++) {
+ temp_len = 0;
+ if (index_length == FW_CFG_INX_LEN_2) /* EX: APCF01 */
+ (void)snprintf(search, SEARCH_LEN, "%s%02d:", block_name, i);
+ else if (index_length == FW_CFG_INX_LEN_3) /* EX: APCF001 */
+ (void)snprintf(search, SEARCH_LEN, "%s%03d:", block_name, i);
+ else
+ (void)snprintf(search, SEARCH_LEN, "%s:", block_name);
+
+ ret = 0;
+
+ ptr = search_result = strstr((char *)searchcontent, search);
+ if (search_result) {
+ /* Add # for comment in bt.cfg */
+ if (ptr > (char *)searchcontent) {
+ ptr--;
+ while ((*ptr == ' ') && (ptr != (char *)searchcontent))
+ ptr--;
+ if (*ptr == '#') {
+ BTMTK_WARN("%s: %s has been ignored", __func__, search);
+ return -1;
+ }
+ }
+
+ memset(temp, 0, TEMP_LEN);
+ search_result = strstr(search_result, "0x");
+ if (search_result == NULL) {
+ BTMTK_ERR("%s: search_result is NULL", __func__);
+ return -1;
+ }
+
+ /* find next line as end of this command line, if NULL means last line */
+ next_block = strstr(search_result, ":");
+ if (next_block == NULL)
+ BTMTK_WARN("%s: if NULL means last line", __func__);
+
+ /* Add HCI packet type to front of each command/event */
+ if (!memcmp(block_name, "APCF", sizeof("APCF")) ||
+ !memcmp(block_name, "RADIOOFF", sizeof("RADIOOFF")) ||
+ !memcmp(block_name, "RADIOON", sizeof("RADIOON")) ||
+ !memcmp(block_name, "APCF_RESUME", sizeof("APCF_RESUME")) ||
+ !memcmp(block_name, "VENDOR_CMD", sizeof("VENDOR_CMD")) ||
+ !memcmp(block_name, "PHASE1_WMT_CMD", sizeof("PHASE1_WMT_CMD"))) {
+ temp[0] = 0x01;
+ temp_len++;
+ } else if (!memcmp(block_name, "RADIOOFF_STATUS_EVENT", sizeof("RADIOOFF_STATUS_EVENT")) ||
+ !memcmp(block_name, "RADIOOFF_COMPLETE_EVENT", sizeof("RADIOOFF_COMPLETE_EVENT")) ||
+ !memcmp(block_name, "RADIOON_STATUS_EVENT", sizeof("RADIOON_STATUS_EVENT")) ||
+ !memcmp(block_name, "RADIOON_COMPLETE_EVENT", sizeof("RADIOON_COMPLETE_EVENT"))) {
+ temp[0] = 0x04;
+ temp_len++;
+ }
+
+ do {
+ search_end = strstr(search_result, ",");
+ if (search_end == NULL) {
+ BTMTK_ERR("%s: Search_end is NULL", __func__);
+ break;
+ }
+
+ if (search_end - search_result != CHAR2HEX_SIZE) {
+ BTMTK_ERR("%s: Incorrect Format in %s", __func__, search);
+ break;
+ }
+
+ memset(number, 0, CHAR2HEX_SIZE + 1);
+ memcpy(number, search_result, CHAR2HEX_SIZE);
+ ret = kstrtoul(number, 0, &parsing_result);
+ if (ret == 0) {
+ if (temp_len >= TEMP_LEN) {
+ BTMTK_ERR("%s: %s data over %d", __func__, search, TEMP_LEN);
+ break;
+ }
+ temp[temp_len] = parsing_result;
+ temp_len++;
+ } else {
+ BTMTK_WARN("%s: %s kstrtoul fail: %d", __func__, search, ret);
+ break;
+ }
+ search_result = strstr(search_end, "0x");
+ if (search_result == NULL) {
+ BTMTK_ERR("%s: search_result is NULL", __func__);
+ break;
+ }
+ } while (search_result < next_block || (search_result && next_block == NULL));
+ } else
+ BTMTK_DBG("%s: %s is not found in %d", __func__, search, i);
+
+ if (temp_len && temp_len < TEMP_LEN) {
+ BTMTK_INFO("%s: %s found & stored in %d", __func__, search, i);
+ save_content[i].content = kzalloc(temp_len, GFP_KERNEL);
+ if (save_content[i].content == NULL) {
+ BTMTK_ERR("%s: Allocate memory fail(%d)", __func__, i);
+ return -ENOMEM;
+ }
+ memcpy(save_content[i].content, temp, temp_len);
+ save_content[i].length = temp_len;
+ BTMTK_DBG_RAW(save_content[i].content, save_content[i].length, "%s", search);
+ }
+ }
+
+ return ret;
+}
+
+int btmtk_load_code_from_setting_files(char *setting_file_name,
+ struct device *dev, u32 *code_len, struct btmtk_dev *bdev)
+{
+ int err = 0;
+ const struct firmware *fw_entry = NULL;
+
+ *code_len = 0;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: g_data is NULL!!", __func__);
+ err = -1;
+ goto end;
+ }
+
+ BTMTK_INFO("%s: begin setting_file_name = %s", __func__, setting_file_name);
+ err = request_firmware(&fw_entry, setting_file_name, dev);
+ if (err != 0 || fw_entry == NULL) {
+ BTMTK_INFO("%s: request_firmware fail, maybe file %s not exist, err = %d, fw_entry = %p",
+ __func__, setting_file_name, err, fw_entry);
+ if (fw_entry)
+ release_firmware(fw_entry);
+ err = -2;
+ goto end;
+ }
+
+ BTMTK_INFO("%s: setting file request_firmware size %zu success", __func__, fw_entry->size);
+ if (bdev->setting_file != NULL) {
+ kfree(bdev->setting_file);
+ bdev->setting_file = NULL;
+ }
+ bdev->setting_file = kzalloc(fw_entry->size + 1, GFP_KERNEL); /* alloc setting file memory */
+ if (bdev->setting_file == NULL) {
+ BTMTK_ERR("%s: kzalloc size %zu failed!!", __func__, fw_entry->size);
+ release_firmware(fw_entry);
+ err = -3;
+ goto end;
+ }
+
+ memcpy(bdev->setting_file, fw_entry->data, fw_entry->size);
+ bdev->setting_file[fw_entry->size] = '\0';
+
+ *code_len = fw_entry->size;
+ release_firmware(fw_entry);
+
+ BTMTK_INFO("%s: setting_file len (%d) assign done", __func__, *code_len);
+end:
+ return err;
+}
+
+#if (USE_DEVICE_NODE == 0)
+static bool btmtk_parse_bt_cfg_file(char *item_name,
+ char *text, u8 *searchcontent)
+{
+ bool ret = true;
+ int temp_len = 0;
+ char search[SEARCH_LEN];
+ char *ptr = NULL, *p = NULL;
+ char *temp = text;
+
+ if (text == NULL) {
+ BTMTK_ERR("%s: text param is invalid!", __func__);
+ ret = false;
+ goto out;
+ }
+
+ memset(search, 0, SEARCH_LEN);
+ (void)snprintf(search, SEARCH_LEN, "%s", item_name); /* EX: SUPPORT_UNIFY_WOBLE */
+ p = ptr = strstr((char *)searchcontent, search);
+
+ if (!ptr) {
+ BTMTK_ERR("%s: Can't find %s\n", __func__, item_name);
+ ret = false;
+ goto out;
+ }
+
+ if (p > (char *)searchcontent) {
+ p--;
+ while ((*p == ' ') && (p != (char *)searchcontent))
+ p--;
+ if (*p == '#') {
+ BTMTK_ERR("%s: It's invalid bt cfg item\n", __func__);
+ ret = false;
+ goto out;
+ }
+ }
+
+ p = ptr + strlen(item_name) + 1;
+ ptr = p;
+
+ for (;;) {
+ switch (*p) {
+ case '\n':
+ goto textdone;
+ default:
+ *temp++ = *p++;
+ break;
+ }
+ }
+
+textdone:
+ temp_len = p - ptr;
+ *temp = '\0';
+
+out:
+ return ret;
+}
+
+static void btmtk_bt_cfg_item_value_to_bool(char *item_value, bool *value)
+{
+ unsigned long text_value = 0;
+
+ if (item_value == NULL) {
+ BTMTK_ERR("%s: item_value is NULL!", __func__);
+ return;
+ }
+
+ if (kstrtoul(item_value, 10, &text_value) == 0) {
+ if (text_value == 1)
+ *value = true;
+ else
+ *value = false;
+ } else {
+ BTMTK_WARN("%s: kstrtoul failed!", __func__);
+ }
+}
+
+static void btmtk_load_bt_cfg_item(struct bt_cfg_struct *bt_cfg_content,
+ u8 *searchcontent, struct btmtk_dev *bdev)
+{
+ bool ret = true;
+ char text[TEXT_LEN]; /* save for search text */
+ unsigned long text_value = 0;
+
+ memset(text, 0, TEXT_LEN);
+ ret = btmtk_parse_bt_cfg_file(BT_UNIFY_WOBLE, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_unify_woble);
+ BTMTK_INFO("%s: bt_cfg_content->support_unify_woble = %d", __func__,
+ bt_cfg_content->support_unify_woble);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_UNIFY_WOBLE);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_UNIFY_WOBLE_TYPE, text, searchcontent);
+ if (ret) {
+ if (kstrtoul(text, 10, &text_value) == 0)
+ bt_cfg_content->unify_woble_type = text_value;
+ else
+ BTMTK_WARN("%s: kstrtoul failed %s!", __func__, BT_UNIFY_WOBLE_TYPE);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_UNIFY_WOBLE_TYPE);
+ }
+
+ BTMTK_INFO("%s: bt_cfg_content->unify_woble_type = %d", __func__,
+ bt_cfg_content->unify_woble_type);
+
+ ret = btmtk_parse_bt_cfg_file(BT_WOBLE_BY_EINT, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_woble_by_eint);
+ BTMTK_INFO("%s: bt_cfg_content->support_woble_by_eint = %d", __func__,
+ bt_cfg_content->support_woble_by_eint);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_WOBLE_BY_EINT);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_DONGLE_RESET_PIN, text, searchcontent);
+ if (ret) {
+ if (kstrtoul(text, 10, &text_value) == 0)
+ bt_cfg_content->dongle_reset_gpio_pin = text_value;
+ else
+ BTMTK_WARN("%s: kstrtoul failed %s!", __func__, BT_DONGLE_RESET_PIN);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_DONGLE_RESET_PIN);
+ }
+
+ BTMTK_INFO("%s: bt_cfg_content->dongle_reset_gpio_pin = %d", __func__,
+ bt_cfg_content->dongle_reset_gpio_pin);
+
+ ret = btmtk_parse_bt_cfg_file(BT_RESET_DONGLE, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_dongle_reset);
+ BTMTK_INFO("%s: bt_cfg_content->support_dongle_reset = %d", __func__,
+ bt_cfg_content->support_dongle_reset);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_RESET_DONGLE);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_FULL_FW_DUMP, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_full_fw_dump);
+ BTMTK_INFO("%s: bt_cfg_content->support_full_fw_dump = %d", __func__,
+ bt_cfg_content->support_full_fw_dump);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_FULL_FW_DUMP);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_WOBLE_WAKELOCK, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_woble_wakelock);
+ BTMTK_INFO("%s: bt_cfg_content->support_woble_wakelock = %d", __func__,
+ bt_cfg_content->support_woble_wakelock);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_WOBLE_WAKELOCK);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_WOBLE_FOR_BT_DISABLE, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_woble_for_bt_disable);
+ BTMTK_INFO("%s: bt_cfg_content->support_woble_for_bt_disable = %d", __func__,
+ bt_cfg_content->support_woble_for_bt_disable);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_WOBLE_FOR_BT_DISABLE);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_RESET_STACK_AFTER_WOBLE, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->reset_stack_after_woble);
+ BTMTK_INFO("%s: bt_cfg_content->reset_stack_after_woble = %d", __func__,
+ bt_cfg_content->reset_stack_after_woble);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_RESET_STACK_AFTER_WOBLE);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_AUTO_PICUS, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_auto_picus);
+ BTMTK_INFO("%s: bt_cfg_content->support_auto_picus = %d", __func__,
+ bt_cfg_content->support_auto_picus);
+ if (bt_cfg_content->support_auto_picus == true) {
+ ret = btmtk_load_fw_cfg_setting(BT_AUTO_PICUS_FILTER,
+ &bt_cfg_content->picus_filter, 1, searchcontent, FW_CFG_INX_LEN_NONE);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUTO_PICUS_FILTER);
+
+ ret = btmtk_load_fw_cfg_setting(BT_AUTO_PICUS_ENABLE,
+ &bt_cfg_content->picus_enable, 1, searchcontent, FW_CFG_INX_LEN_NONE);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUTO_PICUS_ENABLE);
+ }
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUTO_PICUS);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_PICUS_TO_HOST, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_picus_to_host);
+ BTMTK_INFO("%s: bt_cfg_content->support_picus_to_host = %d", __func__,
+ bt_cfg_content->support_picus_to_host);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_PICUS_TO_HOST);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_SINGLE_SKU, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_bt_single_sku);
+ BTMTK_INFO("%s: bt_cfg_content->support_bt_single_sku = %d", __func__,
+ bt_cfg_content->support_bt_single_sku);
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_SINGLE_SKU);
+ }
+
+ ret = btmtk_parse_bt_cfg_file(BT_AUDIO_SET, text, searchcontent);
+ if (ret) {
+ btmtk_bt_cfg_item_value_to_bool(text, &bt_cfg_content->support_audio_setting);
+ BTMTK_INFO("%s: bt_cfg_content->support_audio_setting = %d", __func__,
+ bt_cfg_content->support_audio_setting);
+ if (bt_cfg_content->support_audio_setting == true) {
+ ret = btmtk_load_fw_cfg_setting(BT_AUDIO_ENABLE_CMD,
+ &bt_cfg_content->audio_cmd, 1, searchcontent, FW_CFG_INX_LEN_NONE);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUDIO_ENABLE_CMD);
+
+ ret = btmtk_load_fw_cfg_setting(BT_AUDIO_PINMUX_NUM,
+ &bt_cfg_content->audio_pinmux_num, 1, searchcontent, FW_CFG_INX_LEN_NONE);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUDIO_PINMUX_NUM);
+
+ ret = btmtk_load_fw_cfg_setting(BT_AUDIO_PINMUX_MODE,
+ &bt_cfg_content->audio_pinmux_mode, 1, searchcontent, FW_CFG_INX_LEN_NONE);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUDIO_PINMUX_MODE);
+ }
+ } else {
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_AUDIO_SET);
+ bt_cfg_content->support_audio_setting = true; /* default to turn on, for others not update */
+ BTMTK_WARN("%s: %s default turn on %d!", __func__, BT_AUDIO_SET,
+ bt_cfg_content->support_audio_setting);
+ }
+
+ ret = btmtk_load_fw_cfg_setting(BT_PHASE1_WMT_CMD, bt_cfg_content->phase1_wmt_cmd,
+ PHASE1_WMT_CMD_COUNT, searchcontent, FW_CFG_INX_LEN_3);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_PHASE1_WMT_CMD);
+
+ ret = btmtk_load_fw_cfg_setting(BT_VENDOR_CMD, bt_cfg_content->vendor_cmd,
+ VENDOR_CMD_COUNT, searchcontent, FW_CFG_INX_LEN_3);
+ if (ret)
+ BTMTK_WARN("%s: search item %s is invalid!", __func__, BT_VENDOR_CMD);
+
+ /* release setting file memory */
+ if (bdev) {
+ kfree(bdev->setting_file);
+ bdev->setting_file = NULL;
+ }
+}
+
+static void btmtk_load_bt_cfg(char *cfg_name, struct device *dev, struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u32 code_len = 0;
+
+ ret = btmtk_load_code_from_setting_files(cfg_name, dev, &code_len, bdev);
+ if (ret != 0) {
+ BTMTK_ERR("btmtk_usb_load_code_from_setting_files %s failed!!", cfg_name);
+ if (ret != -2)
+ return;
+
+ if (btmtk_load_code_from_setting_files(BT_CFG_NAME, dev, &code_len, bdev) != 0) {
+ BTMTK_ERR("btmtk_usb_load_code_from_setting_files %s failed!!", BT_CFG_NAME);
+ return;
+ }
+ snprintf(bdev->bt_cfg_file_name, MAX_BIN_FILE_NAME_LEN, "%s", BT_CFG_NAME);
+ }
+
+ btmtk_load_bt_cfg_item(&bdev->bt_cfg, bdev->setting_file, bdev);
+}
+#endif // (USE_DEVICE_NODE == 0)
+
+#if ENABLESTP
+static int btmtk_send_set_stp_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[SET_STP_CMD_LEN] = { 0x01, 0x6F, 0xFC, 0x09, 0x01, 0x04, 0x05, 0x00, 0x03, 0x11, 0x0E, 0x00, 0x00};
+ u8 event[SET_STP_EVT_LEN] = { 0x04, 0xE4, 0x06, 0x02, 0x04, 0x02, 0x00, 0x00, 0x03};
+ int ret = 0;
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, SET_STP_CMD_LEN,
+ event, SET_STP_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+static int btmtk_send_set_stp1_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[SET_STP1_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x0C,
+ 0x01, 0x08, 0x08, 0x00, 0x02, 0x01, 0x00, 0x01, 0x08, 0x00, 0x00, 0x80};
+ u8 event[SET_STP1_EVT_LEN] = {0x04, 0xE4, 0x10, 0x02, 0x08,
+ 0x0C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x80, 0x63, 0x76, 0x00, 0x00};
+ int ret = 0;
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, SET_STP1_CMD_LEN,
+ event, SET_STP1_EVT_LEN, 0, 0,
+ BTMTK_TX_CMD_FROM_DRV);
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+#endif
+
+int btmtk_cap_init(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+
+ BTMTK_DBG("%s start", __func__);
+ if (!bdev) {
+ BTMTK_ERR("%s, bdev is NULL!", __func__);
+ ret = -1;
+ goto exit;
+ }
+#if (USE_DEVICE_NODE == 1)
+ bdev->chip_id = 0x6635;
+#else
+ /* Todo read wifi fw version
+ * int wifi_fw_ver;
+
+ * btmtk_cif_write_register(bdev, 0x7C4001C4, 0x00008800);
+ * btmtk_cif_read_register(bdev, 0x7c4f0004, &wifi_fw_ver);
+ * BTMTK_ERR("wifi fw_ver = %04X", wifi_fw_ver);
+ */
+
+ ret = main_info.hif_hook.reg_read(bdev, CHIP_ID, &bdev->chip_id);
+ if (ret < 0) {
+ BTMTK_ERR("read chip id failed");
+ ret = -EIO;
+ goto exit;
+ } else {
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id)) {
+ ret = main_info.hif_hook.reg_read(bdev, FLAVOR, &bdev->flavor);
+ if (ret < 0) {
+ BTMTK_ERR("read flavor id failed");
+ ret = -EIO;
+ goto exit;
+ }
+ ret = main_info.hif_hook.reg_read(bdev, FW_VERSION, &bdev->fw_version);
+ if (ret < 0) {
+ BTMTK_ERR("read fw version failed");
+ ret = -EIO;
+ goto exit;
+ }
+ } else {
+ BTMTK_ERR("Unknown Mediatek device(%04X)\n", bdev->chip_id);
+ ret = -EIO;
+ goto exit;
+ }
+ }
+
+ BTMTK_INFO("%s: Chip ID = 0x%x", __func__, bdev->chip_id);
+ BTMTK_INFO("%s: flavor = 0x%x", __func__, bdev->flavor);
+ BTMTK_INFO("%s: FW Ver = 0x%x", __func__, bdev->fw_version);
+
+ memset(bdev->rom_patch_bin_file_name, 0, MAX_BIN_FILE_NAME_LEN);
+ if ((bdev->fw_version & 0xff) == 0xff) {
+ BTMTK_ERR("%s: failed, wrong FW version : 0x%x !", __func__, bdev->fw_version);
+ ret = -1;
+ goto exit;
+ }
+
+ if (is_mt7961(bdev->chip_id)) {
+ if (bdev->flavor & DUAL_BT_FLAG)
+ bdev->dualBT = 1;
+ else
+ bdev->dualBT = 0;
+ } else {
+ bdev->dualBT = 0;
+ }
+
+ /* Bin filename format : "BT_RAM_CODE_MT%04x_%x_%x_hdr.bin"
+ * $$$$ : chip id
+ * % : fw version & 0xFF + 1 (in HEX)
+ */
+ if (is_mt7902(bdev->chip_id)) {
+ /* 7902 can't use the same rule to recognize */
+ bdev->flavor = 0;
+ } else {
+ bdev->flavor = (bdev->flavor & 0x00000080) >> 7;
+ }
+ BTMTK_INFO("%s: flavor1 = 0x%x", __func__, bdev->flavor);
+
+ /* if flavor equals 1, it represent 7920, else it represent 7921 */
+ if (bdev->flavor) {
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "BT_RAM_CODE_MT%04x_1a_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ (void)snprintf(bdev->bt_cfg_file_name, MAX_BIN_FILE_NAME_LEN, "%s%x_1a_%x.%s", BT_CFG_NAME_PREFIX,
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ } else {
+ (void)snprintf(bdev->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1);
+ (void)snprintf(bdev->bt_cfg_file_name, MAX_BIN_FILE_NAME_LEN, "%s%x_1_%x.%s", BT_CFG_NAME_PREFIX,
+ bdev->chip_id & 0xffff, (bdev->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ }
+
+ BTMTK_INFO("%s: rom patch file name is %s, bt_cfg_file_name is %s", __func__,
+ bdev->rom_patch_bin_file_name, bdev->bt_cfg_file_name);
+
+ memset(bdev->bdaddr, 0, BD_ADDRESS_SIZE);
+#endif
+exit:
+ return ret;
+}
+
+static int btmtk_send_vendor_cfg(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u16 index = 0;
+ uint8_t event[EVT_HDR_LEN] = { 0x04, 0x0E };
+
+ BTMTK_INFO("%s enter", __func__);
+
+ for (index = 0; index < VENDOR_CMD_COUNT; index++) {
+ if (bdev->bt_cfg.vendor_cmd[index].content &&
+ bdev->bt_cfg.vendor_cmd[index].length) {
+ ret = btmtk_main_send_cmd(bdev,
+ bdev->bt_cfg.vendor_cmd[index].content,
+ bdev->bt_cfg.vendor_cmd[index].length,
+ event, EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s: Send vendor cmd failed(%d)! Index: %d",
+ __func__, ret, index);
+ goto exit;
+ }
+
+ BTMTK_INFO_RAW(bdev->bt_cfg.vendor_cmd[index].content,
+ bdev->bt_cfg.vendor_cmd[index].length, "send vendor cmd");
+ }
+ }
+
+exit:
+ BTMTK_INFO("%s exit", __func__);
+ return ret;
+}
+
+static int btmtk_send_phase1_wmt_cfg(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u16 index = 0;
+ uint8_t event[EVT_HDR_LEN] = { 0x04, 0xE4 };
+
+ BTMTK_INFO("%s", __func__);
+
+ for (index = 0; index < PHASE1_WMT_CMD_COUNT; index++) {
+ if (bdev->bt_cfg.phase1_wmt_cmd[index].content &&
+ bdev->bt_cfg.phase1_wmt_cmd[index].length) {
+ ret = btmtk_main_send_cmd(bdev,
+ bdev->bt_cfg.phase1_wmt_cmd[index].content,
+ bdev->bt_cfg.phase1_wmt_cmd[index].length,
+ event, EVT_HDR_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: Send phase1 wmt cmd failed(%d)! Index: %d",
+ __func__, ret, index);
+ goto exit;
+ }
+
+ BTMTK_INFO_RAW(bdev->bt_cfg.phase1_wmt_cmd[index].content,
+ bdev->bt_cfg.phase1_wmt_cmd[index].length, "send wmt cmd");
+ }
+ }
+
+exit:
+ BTMTK_INFO("%s exit", __func__);
+ return ret;
+}
+
+int btmtk_send_init_cmds(struct btmtk_dev *bdev)
+{
+ int ret = -1;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is NULL !", __func__);
+ goto exit;
+ }
+
+ BTMTK_INFO("%s", __func__);
+
+#if ENABLESTP
+ btmtk_send_set_stp_cmd(bdev);
+ btmtk_send_set_stp1_cmd(bdev);
+#endif
+ ret = btmtk_calibration_flow(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_calibration_flow failed!", __func__);
+ goto exit;
+ }
+ ret = btmtk_send_wmt_power_on_cmd(bdev);
+ if (ret < 0) {
+ if (bdev->power_state != BTMTK_DONGLE_STATE_POWER_ON) {
+ BTMTK_ERR("%s, btmtk_send_wmt_power_on_cmd failed!", __func__);
+ if (main_info.reset_stack_flag == HW_ERR_NONE)
+ main_info.reset_stack_flag = HW_ERR_CODE_POWER_ON;
+ }
+ goto exit;
+ }
+
+ ret = btmtk_send_phase1_wmt_cfg(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("btmtk_send_wmt_cfg failed");
+ goto exit;
+ }
+
+ if (bdev->bt_cfg.support_auto_picus == true &&
+ (bdev->bt_cfg.support_picus_to_host == true || atomic_read(&bmain_info->fwlog_ref_cnt) != 0)) {
+ if (btmtk_picus_enable(bdev) < 0) {
+ BTMTK_ERR("send picus filter param failed");
+ ret = -1;
+ goto exit;
+ }
+ }
+
+ ret = btmtk_send_vendor_cfg(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("btmtk_send_vendor_cfg failed");
+ goto exit;
+ }
+
+exit:
+ return ret;
+}
+
+
+int btmtk_send_deinit_cmds(struct btmtk_dev *bdev)
+{
+ int ret = -1;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is NULL !", __func__);
+ return ret;
+ }
+
+ BTMTK_INFO("%s", __func__);
+
+ if (bdev->bt_cfg.support_auto_picus == true &&
+ (bdev->bt_cfg.support_picus_to_host == true || atomic_read(&bmain_info->fwlog_ref_cnt) != 0)) {
+ if (btmtk_picus_disable(bdev) < 0) {
+ BTMTK_ERR("send picus filter param failed");
+ return -1;
+ }
+ }
+
+ ret = btmtk_send_wmt_power_off_cmd(bdev);
+ if (bdev->power_state != BTMTK_DONGLE_STATE_POWER_OFF) {
+ BTMTK_WARN("Power off failed, reset it");
+ if (main_info.reset_stack_flag == HW_ERR_NONE)
+ main_info.reset_stack_flag = HW_ERR_CODE_POWER_OFF;
+ }
+
+ return ret;
+}
+
+int btmtk_send_assert_cmd(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ int state;
+ struct sk_buff *skb = NULL;
+#if (USE_DEVICE_NODE == 0)
+ u8 cmd[ASSERT_CMD_LEN] = { 0x01, 0x6F, 0xFC, 0x05, 0x01, 0x02, 0x01, 0x00, 0x08 };
+#else
+ u8 cmd[ASSERT_CMD_LEN] = { 0x01, 0x5B, 0xFD, 0x00 };
+#endif
+
+ if (!bdev) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state == BTMTK_STATE_FW_DUMP || state == BTMTK_STATE_SUSPEND ||
+ state == BTMTK_STATE_SEND_ASSERT || state == BTMTK_STATE_CLOSED) {
+ BTMTK_WARN("%s: FW dumping already or in suspend state don't send assert, state = %d!!!",
+ __func__, state);
+ return ret;
+ }
+
+ BTMTK_INFO("%s: send assert cmd", __func__);
+
+ skb = alloc_skb(ASSERT_CMD_LEN + BT_SKB_RESERVE, GFP_ATOMIC);
+ if (!skb) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ goto exit;
+ }
+ bt_cb(skb)->pkt_type = HCI_COMMAND_PKT;
+ memcpy(skb->data, cmd, ASSERT_CMD_LEN);
+ skb->len = ASSERT_CMD_LEN;
+
+#if (SLEEP_ENABLE == 0)
+ ret = main_info.hif_hook.send_cmd(bdev, skb, WMT_DELAY_TIMES, RETRY_TIMES, (int)BTMTK_TX_CMD_FROM_DRV);
+#else
+ ret = main_info.hif_hook.send_cmd(bdev, skb, WMT_DELAY_TIMES, RETRY_TIMES, (int)BTMTK_TX_PKT_SEND_DIRECT);
+#endif
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ kfree_skb(skb);
+ skb = NULL;
+ btmtk_reset_trigger(bdev);
+ } else {
+ btmtk_reset_timer_update(bdev);
+ BTMTK_INFO("%s: OK", __func__);
+ btmtk_set_chip_state(bdev, BTMTK_STATE_SEND_ASSERT);
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_send_txpower_cmd(struct btmtk_dev *bdev)
+{
+ /**
+ * TCI Set TX Power Command
+ * 01 2C FC 0C QQ 00 00 00 XX YY ZZ GG AA BB CC DD
+ * QQ: EDR init TX power dbm // the value is equal to EDR MAX
+ * XX: BLE TX power dbm
+ * YY: EDR MAX TX power dbm
+ * ZZ: Enable LV9
+ * GG: 3db diff mode
+ * AA: [5:4] Indicator // [5] 1: command send to BT1, [4] 1: command send to BT0
+ * [3:0] Resolution // 0: 1dBm, 1: 0.5dBm, 2: 0.25dBm
+ * BB: BLE 2M
+ * CC: BLE S2
+ * DD: BLE S8
+ */
+
+ u8 cmd[TXPOWER_CMD_LEN] = { 0x01, 0x2C, 0xFC, 0x0C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ u8 event[TXPOWER_EVT_LEN] = { 0x04, 0x0E, 0x04, 0x01, 0x2C, 0xFC, 0x00 };
+ int ret = 0;
+
+ cmd[4] = (u8)main_info.PWS.EDR_Max;
+ cmd[8] = (u8)main_info.PWS.BLE_1M;
+ cmd[9] = (u8)main_info.PWS.EDR_Max;
+ cmd[10] = (u8)main_info.PWS.LV9;
+ cmd[11] = (u8)main_info.PWS.DM;
+ cmd[12] = (u8)main_info.PWS.IR;
+ cmd[13] = (u8)main_info.PWS.BLE_2M;
+ cmd[14] = (u8)main_info.PWS.BLE_LR_S2;
+ cmd[15] = (u8)main_info.PWS.BLE_LR_S8;
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, TXPOWER_CMD_LEN,
+ event, TXPOWER_EVT_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_PKT_FROM_HOST);
+
+ if (ret < 0)
+ BTMTK_ERR("%s failed!!", __func__);
+ else
+ BTMTK_INFO("%s: OK", __func__);
+
+ return ret;
+}
+
+static int btmtk_set_power_value(char *str, int resolution, int is_edr)
+{
+ int power = ERR_PWR, integer = 0, decimal = 0;
+ char *ptr = NULL;
+
+ if (resolution == RES_DOT_25) {
+ /* XX.YY => XX.YY/0.25 = XX*4 + YY/25 */
+ if (strstr(str, ".")) {
+ ptr = strsep(&str, ".");
+ if (ptr == NULL)
+ return -1;
+ if (kstrtoint(ptr, 0, &integer) != 0) {
+ BTMTK_ERR("Read integer Fail");
+ return -1;
+ }
+
+ if (kstrtoint(str, 0, &decimal) != 0) {
+ BTMTK_ERR("Read decimal Fail");
+ return -1;
+ }
+
+ if (decimal != 25 && decimal != 75 && decimal != 5 && decimal != 50)
+ return ERR_PWR;
+ if (decimal == 5)
+ decimal = 50;
+ if (integer >= 0)
+ power = integer * 4 + decimal / 25;
+ else
+ power = integer * 4 - decimal / 25;
+ } else {
+ if (kstrtoint(str, 0, &integer) != 0) {
+ BTMTK_ERR("Read integer Fail");
+ return -1;
+ }
+ power = integer * 4;
+ }
+
+ BTMTK_DBG("%s: power = %d", __func__, power);
+
+ if (is_edr) {
+ if (power > EDR_MAX_R2 || power < EDR_MIN_R2)
+ return ERR_PWR;
+ if (power >= EDR_MIN_LV9_R2)
+ main_info.PWS.LV9 = 1;
+ } else if (!is_edr && (power > BLE_MAX_R2 || power < BLE_MIN_R2))
+ return ERR_PWR;
+ } else if (resolution == RES_DOT_5) {
+ /* XX.YY => XX.YY/0.5 = XX*2 + YY/5 */
+ if (strstr(str, ".")) {
+ ptr = strsep(&str, ".");
+ if (ptr == NULL)
+ return -1;
+ if (kstrtoint(ptr, 0, &integer) != 0) {
+ BTMTK_ERR("Read integer Fail");
+ return -1;
+ }
+
+ if (kstrtoint(str, 0, &decimal) != 0) {
+ BTMTK_ERR("Read decimal Fail");
+ return -1;
+ }
+
+ if (decimal != 5)
+ return ERR_PWR;
+ if (integer >= 0)
+ power = integer * 2 + decimal / 5;
+ if (integer < 0)
+ power = integer * 2 - decimal / 5;
+ } else {
+ if (kstrtoint(str, 0, &integer) != 0) {
+ BTMTK_ERR("Read integer Fail");
+ return -1;
+ }
+ power = integer * 2;
+ }
+
+ BTMTK_DBG("%s: power = %d", __func__, power);
+
+ if (is_edr) {
+ if (power > EDR_MAX_R1 || power < EDR_MIN_R1)
+ return ERR_PWR;
+ if (power >= EDR_MIN_LV9_R1)
+ main_info.PWS.LV9 = 1;
+ } else if (!is_edr && (power > BLE_MAX_R1 || power < BLE_MIN_R1))
+ return ERR_PWR;
+ } else if (resolution == RES_1) {
+ if (kstrtoint(str, 0, &power) != 0) {
+ BTMTK_ERR("Read power Fail");
+ return -1;
+ }
+
+ BTMTK_DBG("%s: power = %d", __func__, power);
+
+ if (is_edr) {
+ if (power > EDR_MAX || power < EDR_MIN)
+ return ERR_PWR;
+ if (power >= EDR_MIN_LV9)
+ main_info.PWS.LV9 = 1;
+ } else if (!is_edr && (power > BLE_MAX || power < BLE_MIN))
+ return ERR_PWR;
+ }
+
+ return power;
+}
+
+static int btmtk_check_power_resolution(char *str)
+{
+ if (str == NULL)
+ return -1;
+ if (strstr(str, ".25") || strstr(str, ".75"))
+ return RES_DOT_25;
+ if (strstr(str, ".5"))
+ return RES_DOT_5;
+ if (!strstr(str, ".") || strstr(str, ".0"))
+ return RES_1;
+ return -1;
+}
+
+static void btmtk_init_power_setting_struct(void)
+{
+ main_info.PWS.BLE_1M = 0;
+ main_info.PWS.EDR_Max = 0;
+ main_info.PWS.LV9 = 0;
+ main_info.PWS.DM = 0;
+ main_info.PWS.IR = 0;
+ main_info.PWS.BLE_2M = 0;
+ main_info.PWS.BLE_LR_S2 = 0;
+ main_info.PWS.BLE_LR_S8 = 0;
+}
+
+static int btmtk_parse_power_table(char *context)
+{
+ char *ptr = NULL;
+ int step = 0, temp;
+ int resolution;
+ int power;
+
+ if (context == NULL) {
+ BTMTK_ERR("%s context is NULL", __func__);
+ return -1;
+ }
+
+ BTMTK_INFO("%s", __func__);
+ btmtk_init_power_setting_struct();
+
+ /* Send to BT0? BT1? */
+ if (strstr(context, "BT0")) {
+ BTMTK_INFO("Parse power for BT0");
+ main_info.PWS.IR |= 0x10;
+ context += strlen("[BT0]");
+ } else if (strstr(context, "BT1")) {
+ BTMTK_INFO("Parse power for BT1");
+ main_info.PWS.IR |= 0x20;
+ context += strlen("[BT1]");
+ } else {
+ BTMTK_ERR("%s BT indicator error", __func__);
+ return -1;
+ }
+
+ resolution = btmtk_check_power_resolution(context);
+ if (resolution == -1) {
+ BTMTK_ERR("Check resolution fail");
+ return -1;
+ }
+
+ main_info.PWS.IR |= resolution;
+ BTMTK_INFO("%s: resolution = %d", __func__, resolution);
+
+ while ((ptr = strsep(&context, ",")) != NULL) {
+ while (*ptr == '\t' || *ptr == ' ')
+ ptr++;
+
+ switch (step) {
+ /* BR_EDR_PWR_MODE */
+ case CHECK_SINGLE_SKU_PWR_MODE:
+ if (kstrtoint(ptr, 0, &temp) == 0) {
+ if (temp == 0 || temp == 1) {
+ main_info.PWS.DM = temp;
+ step++;
+ continue;
+ } else {
+ BTMTK_ERR("PWR MODE value wrong");
+ return -1;
+ }
+ } else {
+ BTMTK_ERR("Read PWR MODE Fail");
+ return -1;
+ }
+ break;
+ /* Parse EDR MAX */
+ case CHECK_SINGLE_SKU_EDR_MAX:
+ power = btmtk_set_power_value(ptr, resolution, 1);
+ if (power == ERR_PWR) {
+ BTMTK_ERR("EDR MAX value wrong");
+ return -1;
+ }
+ main_info.PWS.EDR_Max = power;
+ step++;
+ break;
+ /* Parse BLE Default */
+ case CHECK_SINGLE_SKU_BLE:
+ power = btmtk_set_power_value(ptr, resolution, 0);
+ if (power == ERR_PWR) {
+ BTMTK_ERR("BLE value wrong");
+ return -1;
+ }
+ main_info.PWS.BLE_1M = power;
+ step++;
+ break;
+ /* Parse BLE 2M */
+ case CHECK_SINGLE_SKU_BLE_2M:
+ power = btmtk_set_power_value(ptr, resolution, 0);
+ if (power == ERR_PWR) {
+ BTMTK_ERR("BLE 2M value wrong");
+ return -1;
+ }
+ main_info.PWS.BLE_2M = power;
+ step++;
+ break;
+ /* Parse BLE long range S2 */
+ case CHECK_SINGLE_SKU_BLE_LR_S2:
+ power = btmtk_set_power_value(ptr, resolution, 0);
+ if (power == ERR_PWR) {
+ BTMTK_ERR("BLE LR S2 value wrong");
+ return -1;
+ }
+ main_info.PWS.BLE_LR_S2 = power;
+ step++;
+ break;
+ /* Parse BLE long range S8 */
+ case CHECK_SINGLE_SKU_BLE_LR_S8:
+ power = btmtk_set_power_value(ptr, resolution, 0);
+ if (power == ERR_PWR) {
+ BTMTK_ERR("BLE LR S8 value wrong");
+ return -1;
+ }
+ main_info.PWS.BLE_LR_S8 = power;
+ step++;
+ break;
+ default:
+ BTMTK_ERR("%s step is wrong: %d", __func__, step);
+ break;
+ }
+ continue;
+ }
+
+ return step;
+}
+
+static void btmtk_send_txpower_cmd_to_all_interface(void)
+{
+ int i, ret;
+ struct btmtk_dev *bdev = NULL;
+
+ for (i = 0; i < btmtk_intf_num; i++) {
+ if (g_bdev[i]->hdev != NULL) {
+ bdev = g_bdev[i];
+ BTMTK_INFO("send to %d", i);
+ ret = btmtk_send_txpower_cmd(bdev);
+ if (ret < 0)
+ BTMTK_ERR("Device %d send txpower cmd fail", i);
+ }
+ }
+}
+
+static void btmtk_requset_country_cb(const struct firmware *fw, void *context)
+{
+ char *ptr, *data, *p_data = NULL;
+ char *country = NULL;
+ int ret = 0;
+ bool find_country = false;
+ bool read_next = false;
+
+ if (fw == NULL) {
+ BTMTK_ERR("fw is NULL");
+ return;
+ }
+
+ BTMTK_INFO("%s request %s success", __func__, DEFAULT_COUNTRY_TABLE_NAME);
+ p_data = data = kzalloc(fw->size, GFP_KERNEL);
+ if (data == NULL) {
+ BTMTK_WARN("%s allocate memory fail (data)", __func__);
+ goto exit;
+ }
+
+ memcpy(data, fw->data, fw->size);
+ while ((ptr = strsep(&p_data, "\n")) != NULL) {
+ /* If the '#' in front of the line, ignore this line */
+ if (*ptr == '#')
+ continue;
+
+ /* Set power for BT1 */
+ if (read_next) {
+ if (strncmp(ptr, "[BT1]", 5) == 0) {
+ ret = btmtk_parse_power_table(ptr);
+ if (ret != CHECK_SINGLE_SKU_ALL) {
+ BTMTK_ERR("Parse power fail, ret = %d", ret);
+ break;
+ }
+
+ btmtk_send_txpower_cmd_to_all_interface();
+ } else {
+ BTMTK_INFO("No power data for BT1");
+ }
+ break;
+ }
+
+ if (find_country) {
+ ret = btmtk_parse_power_table(ptr);
+ /* Check if the next line has power value for BT1 */
+ read_next = true;
+ if (ret != CHECK_SINGLE_SKU_ALL) {
+ BTMTK_ERR("Parse power fail, ret = %d", ret);
+ continue;
+ }
+
+ btmtk_send_txpower_cmd_to_all_interface();
+ continue;
+ }
+
+ while ((country = strsep(&ptr, ",[]")) != NULL) {
+ if (strlen(country) != COUNTRY_CODE_LEN)
+ continue;
+ if (strcmp(country, main_info.PWS.country_code) == 0) {
+ find_country = true;
+ break;
+ }
+ }
+ }
+ kfree(data);
+
+ if (find_country == false)
+ BTMTK_ERR("Can't find country in the table");
+
+exit:
+ release_firmware(fw);
+}
+
+static int btmtk_load_country_table(struct btmtk_dev *bdev)
+{
+ int err = 0;
+
+ if (bdev->country_file_name)
+ err = request_firmware_nowait(THIS_MODULE, true,
+ bdev->country_file_name, NULL, GFP_KERNEL, NULL,
+ btmtk_requset_country_cb);
+ else
+ BTMTK_WARN("%s country_file_name is null", __func__);
+
+ return err;
+}
+
+void btmtk_set_country_code_from_wifi(char *code)
+{
+ int i;
+ struct btmtk_dev *bdev = NULL;
+
+ if (!code)
+ return;
+
+ if (strlen(code) == COUNTRY_CODE_LEN) {
+ BTMTK_INFO("%s country code is %s", __func__, code);
+ memcpy(main_info.PWS.country_code, code, sizeof(main_info.PWS.country_code));
+ for (i = 0; i < btmtk_intf_num; i++) {
+ if (g_bdev[i]->hdev != NULL) {
+ bdev = g_bdev[i];
+ if (bdev->bt_cfg.support_bt_single_sku) {
+ btmtk_load_country_table(bdev);
+ break;
+ }
+ }
+ }
+ } else {
+ BTMTK_INFO("%s country code is not valid", __func__);
+ }
+}
+/* Pin-Hao: remove export symbol to build 2 same BT driver for SLT */
+#if (USE_DEVICE_NODE == 0)
+EXPORT_SYMBOL_GPL(btmtk_set_country_code_from_wifi);
+#endif
+
+/**
+ * Kernel HCI Interface Registeration
+ */
+static int bt_flush(struct hci_dev *hdev)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+
+ if (is_mt66xx(bdev->chip_id))
+ return main_info.hif_hook.flush(bdev);
+ else
+ return 0;
+}
+
+static int bt_close(struct hci_dev *hdev)
+{
+ int ret = -1;
+ int state = BTMTK_STATE_INIT;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = NULL;
+
+ if (!hdev) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ return ret;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is invalid!", __func__);
+ return ret;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not allow close(%d)", __func__, fstate);
+ goto err;
+ }
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_CLOSING);
+
+ state = btmtk_get_chip_state(bdev);
+
+#if (USE_DEVICE_NODE == 1)
+ if (state == BTMTK_STATE_FW_DUMP || state == BTMTK_STATE_SEND_ASSERT
+ || state == BTMTK_STATE_SUBSYS_RESET) {
+ BTMTK_WARN("%s: fw dump or assert ongoing , can't close yet state[%d]", __func__, state);
+ if (!wait_for_completion_timeout(&bdev->dump_comp, msecs_to_jiffies(WAIT_FW_DUMP_TIMEOUT)))
+ BTMTK_ERR("%s: uanble to finish coredump in 15s", __func__);
+ goto exit;
+ }
+#endif
+
+ if (state != BTMTK_STATE_WORKING && state != BTMTK_STATE_STANDBY) {
+ /* If hif disconnect occurs,
+ * it will call cif_mutex_lock and release hci device.
+ * Release hci device will call bt_close.
+ * It must return with this,
+ * otherwise the below cif_mutex_lock will cause deadlock
+ */
+ BTMTK_WARN("%s: not in working state and standby state(%d).", __func__, state);
+ goto exit;
+ }
+
+ if (main_info.hif_hook.cif_mutex_lock)
+ main_info.hif_hook.cif_mutex_lock(bdev);
+
+ state = btmtk_get_chip_state(bdev);
+ BTMTK_INFO("%s, enter, state[%d]", __func__, state);
+
+ if (state != BTMTK_STATE_WORKING && state != BTMTK_STATE_STANDBY) {
+ /* It's for the case that
+ * bt_close and hif_disconnect occur at the same time
+ */
+ BTMTK_WARN("%s: not in working state and standby state(%d).", __func__, state);
+ goto unlock;
+ }
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ /* Don't send init cmd for DVT
+ * Such as Lowpower DVT
+ */
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_OFF;
+ BTMTK_INFO("%s, SKIP btmtk_send_deinit_cmds", __func__);
+#else
+ if (state != BTMTK_STATE_STANDBY && main_info.reset_stack_flag != HW_ERR_CODE_CORE_DUMP
+ && main_info.reset_stack_flag != HW_ERR_CODE_CHIP_RESET) {
+ ret = btmtk_send_deinit_cmds(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_deinit_cmds failed", __func__);
+ goto unlock;
+ }
+ } else
+ BTMTK_WARN("%s, SKIP by state[%d], reset_stack_flag[%d]", __func__, state, main_info.reset_stack_flag);
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ /* Flush RX works */
+ flush_work(&bdev->rx_work);
+ flush_work(&bdev->dynamic_fwdl_work);
+
+ /* Drop queues */
+ skb_queue_purge(&bdev->rx_q);
+
+#if (USE_DEVICE_NODE == 0)
+ main_info.hif_hook.close(hdev);
+#endif
+
+unlock:
+ if (main_info.hif_hook.cif_mutex_unlock)
+ main_info.hif_hook.cif_mutex_unlock(bdev);
+exit:
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_CLOSED);
+#if (USE_DEVICE_NODE == 1)
+ /* avoid reset start at new bt on */
+ btmtk_reset_timer_del(bdev);
+ main_info.hif_hook.close(hdev);
+ state = btmtk_get_chip_state(bdev);
+ /* after fops set closed, would not get any rx data */
+ /* so if chip_state still fw_dump, need to release wakelock and coredump end*/
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_ERR("%s: end with chip_state still dumping", __func__);
+ btmtk_fwdump_wake_unlock();
+ connv3_coredump_end(main_info.hif_hook.coredump_handler, "BT coredump fail");
+ }
+ if (state != BTMTK_STATE_DISCONNECT)
+ btmtk_set_chip_state(bdev, BTMTK_STATE_CLOSED);
+
+#endif
+
+err:
+ main_info.reset_stack_flag = HW_ERR_NONE;
+ bdev->get_hci_reset = 0;
+
+ BTMTK_INFO("%s: state[%d], reset_stack_flag[%d]", __func__, state, main_info.reset_stack_flag);
+ return 0;
+}
+
+int bt_open(struct hci_dev *hdev)
+{
+ int ret = -1;
+ int state = BTMTK_STATE_INIT;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = NULL;
+ void (*rlm_get_alpha2)(char *);
+ const char *wifi_func_name = "rlm_get_alpha2";
+ char alpha2[5];
+
+ BTMTK_INFO("%s: MTK BT Driver Version : %s", __func__, VERSION);
+ DUMP_TIME_STAMP("open_start");
+
+ if (!hdev) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ return -EFAULT;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (!bdev) {
+ BTMTK_ERR("%s: bdev is invalid", __func__);
+ return -EFAULT;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate == BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops opened!", __func__);
+ return -EIO;
+ }
+
+ if ((fstate == BTMTK_FOPS_STATE_CLOSING) ||
+ (fstate == BTMTK_FOPS_STATE_OPENING)) {
+ BTMTK_WARN("%s: fops open/close is on-going !", __func__);
+ return -EAGAIN;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state == BTMTK_STATE_INIT || state == BTMTK_STATE_DISCONNECT) {
+ BTMTK_WARN("%s: chip_state[%d] is init or disconnect!", __func__, state);
+ return -EAGAIN;
+ }
+
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_OPENING);
+
+ if (main_info.hif_hook.pre_open) {
+ ret = main_info.hif_hook.pre_open(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s: pre_open fail", __func__);
+ goto failed;
+ }
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_WORKING && state != BTMTK_STATE_STANDBY) {
+ BTMTK_WARN("%s: not in working state and standby state(%d).", __func__, state);
+ ret = -ENODEV;
+ goto failed;
+ }
+
+ BTMTK_INFO("%s state[%d], fstate[%d]", __func__, state, fstate);
+
+ ret = main_info.hif_hook.open(hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_open failed", __func__);
+ goto failed;
+ }
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ /* Don't send init cmd for DVT
+ * Such as Lowpower DVT
+ */
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_ON;
+ BTMTK_INFO("%s, SKIP btmtk_send_init_cmds", __func__);
+#else
+ ret = btmtk_send_init_cmds(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_init_cmds failed", __func__);
+ goto failed;
+ }
+
+ //ret = btmtk_send_apcf_reserved(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_apcf_reserved failed", __func__);
+ goto failed;
+ }
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ if (main_info.hif_hook.open_done)
+ main_info.hif_hook.open_done(bdev);
+
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_OPENED);
+ main_info.reset_stack_flag = HW_ERR_NONE;
+
+ if (bdev->bt_cfg.support_bt_single_sku) {
+ rlm_get_alpha2 = (void *)btmtk_kallsyms_lookup_name(wifi_func_name);
+
+ if (rlm_get_alpha2) {
+ rlm_get_alpha2(alpha2);
+ if (strlen(alpha2) == COUNTRY_CODE_LEN) {
+ BTMTK_INFO("Wifi set country code %s", alpha2);
+ memcpy(main_info.PWS.country_code, alpha2, sizeof(main_info.PWS.country_code));
+ } else {
+ BTMTK_ERR("Country code length is wrong");
+ }
+ } else {
+ BTMTK_INFO("Wifi didn't set country code");
+ }
+
+ main_info.PWS.country_code[COUNTRY_CODE_LEN] = '\0';
+ if (strcmp(main_info.PWS.country_code, "") != 0)
+ btmtk_load_country_table(bdev);
+ }
+
+ DUMP_TIME_STAMP("open_end");
+ return 0;
+
+failed:
+#if (USE_DEVICE_NODE == 1)
+ main_info.hif_hook.close(hdev);
+ state = btmtk_get_chip_state(bdev);
+ /* if state is disconnect means uart_launcher is disconnected, not set to close state */
+ if (state != BTMTK_STATE_DISCONNECT)
+ btmtk_set_chip_state(bdev, BTMTK_STATE_CLOSED);
+
+#endif
+
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_CLOSED);
+
+ return ret;
+}
+
+static int bt_setup(struct hci_dev *hdev)
+{
+ int ret = 0;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+
+ BTMTK_INFO("%s", __func__);
+
+ if (is_mt66xx(bdev->chip_id)) {
+ ret = main_info.hif_hook.open(hdev);
+ if (ret)
+ BTMTK_ERR("%s: fail", __func__);
+ return ret;
+ }
+ return 0;
+}
+
+static int bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ int ret = -1;
+ int state = BTMTK_STATE_INIT;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ /* parsing commands */
+ u8 fw_coredump_cmd[FW_COREDUMP_CMD_LEN] = { 0x01, 0x5B, 0xFD, 0x00 };
+ u8 fw_coredump_flag = 0;
+ u8 reset_cmd[HCI_RESET_CMD_LEN] = { 0x01, 0x03, 0x0C, 0x00 };
+ struct btmtk_dev *bdev = NULL;
+ unsigned char *skb_tmp = NULL;
+
+ if (hdev == NULL || skb == NULL) {
+ BTMTK_ERR("%s, invalid parameters!", __func__);
+ return -ENODEV;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s, bdev is invalid!", __func__);
+ return -ENODEV;
+ }
+
+ if (main_info.hif_hook.cif_mutex_lock)
+ main_info.hif_hook.cif_mutex_lock(bdev);
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open yet(%d)!", __func__, fstate);
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_WORKING) {
+ BTMTK_WARN_LIMITTED("%s: chip state is not working state[%d]", __func__, state);
+ if (state == BTMTK_STATE_DISCONNECT)
+ ret = -ENODEV;
+ else
+ ret = -EAGAIN;
+ goto exit;
+ }
+
+ if (bdev->power_state == BTMTK_DONGLE_STATE_POWER_OFF) {
+ BTMTK_WARN("%s: dongle state already power off, do not write", __func__);
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ if (main_info.reset_stack_flag) {
+ BTMTK_WARN("%s: reset_stack_flag (%d)!", __func__, main_info.reset_stack_flag);
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ if (!is_mt66xx(bdev->chip_id))
+ btmtk_dispatch_fwlog_bluetooth_kpi(bdev, skb->data, skb->len, hci_skb_pkt_type(skb));
+
+ skb_tmp = skb_push(skb, 1);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed!", __func__);
+ ret = -ENOMEM;
+ goto exit;
+ }
+ memcpy(skb_tmp, &hci_skb_pkt_type(skb), 1);
+#if ENABLESTP
+ skb = mtk_add_stp(bdev, skb);
+#endif
+
+ if (!is_mt66xx(bdev->chip_id)) {
+ /* For Ble ISO packet size */
+ if (memcmp(skb->data, main_info.read_iso_packet_size_cmd,
+ READ_ISO_PACKET_SIZE_CMD_HDR_LEN) == 0) {
+ bdev->iso_threshold = skb->data[READ_ISO_PACKET_SIZE_CMD_HDR_LEN] +
+ (skb->data[READ_ISO_PACKET_SIZE_CMD_HDR_LEN + 1] << 8);
+ BTMTK_INFO("%s: Ble iso pkt size is %d", __func__, bdev->iso_threshold);
+ }
+
+ if (hci_skb_pkt_type(skb) == HCI_COMMAND_PKT) {
+#if (USE_DEVICE_NODE == 0)
+ if (bdev->get_hci_reset == 1) {
+ ret = btmtk_set_audio_setting(bdev);
+ bdev->get_hci_reset = 0;
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_set_audio_setting failed!!", __func__);
+ goto exit;
+ }
+ }
+#endif
+ /* save hci cmd pkt for debug */
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_CMD_STACK, skb->data, skb->len);
+ if (skb->len == FW_COREDUMP_CMD_LEN &&
+ !memcmp(skb->data, fw_coredump_cmd, FW_COREDUMP_CMD_LEN)) {
+ BTMTK_INFO("%s: Dongle FW Assert Triggered by BT Stack!", __func__);
+ fw_coredump_flag = 1;
+ btmtk_reset_timer_update(bdev);
+ btmtk_hci_snoop_print_to_log();
+ } else if (skb->len == HCI_RESET_CMD_LEN &&
+ !memcmp(skb->data, reset_cmd, HCI_RESET_CMD_LEN))
+ BTMTK_INFO("%s: got command: 0x03 0C 00 (HCI_RESET)", __func__);
+ } else if (hci_skb_pkt_type(skb) == HCI_ACLDATA_PKT) {
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_TX_ACL_STACK, skb->data, skb->len);
+ } else if (hci_skb_pkt_type(skb) == HCI_ISO_PKT) {
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_TX_ISO_STACK, skb->data, skb->len);
+ }
+
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send, len = %d ", __func__, skb->len);
+ ret = main_info.hif_hook.send_cmd(bdev, skb, 0, 0, (int)BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ goto exit;
+ }
+ } else {
+ ret = main_info.hif_hook.send_cmd(bdev, skb, 0, 5, (int)BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ goto exit;
+ }
+ }
+
+exit:
+ if (main_info.hif_hook.cif_mutex_unlock)
+ main_info.hif_hook.cif_mutex_unlock(bdev);
+
+ if (ret >= 0 && fw_coredump_flag == 1)
+ btmtk_set_chip_state(bdev, BTMTK_STATE_SEND_ASSERT);
+
+ return ret;
+}
+
+void btmtk_reg_hif_hook(struct hif_hook_ptr *hook)
+{
+ memcpy(&main_info.hif_hook, hook, sizeof(struct hif_hook_ptr));
+}
+
+static void btmtk_dynamic_fwdl_work(struct work_struct *work)
+{
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, dynamic_fwdl_work);
+
+ BTMTK_INFO("%s enter", __func__);
+ btmtk_dynamic_load_rom_patch(bdev, bdev->fw_bin_info);
+}
+
+static void btmtk_rx_work(struct work_struct *work)
+{
+ int err = 0, skip_pkt = 0;
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, rx_work);
+ struct sk_buff *skb;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ int state = 0;
+
+ while ((skb = skb_dequeue(&bdev->rx_q))) {
+ /* BTMTK_DBG_RAW(skb->data, skb->len, "%s, recv evt", __func__); */
+ skip_pkt = btmtk_dispatch_fwlog(bdev, skb);
+ if (skip_pkt != 0) {
+ /* kfree_skb should be moved to btmtk_dispach_pkt */
+ kfree_skb(skb);
+ continue;
+ }
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: len[%d] %02x", __func__,
+ skb->len, hci_skb_pkt_type(skb));
+ if (hci_skb_pkt_type(skb) == HCI_EVENT_PKT) {
+ /* save hci evt pkt for debug */
+ if (skb->data[0] == 0x3E)
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_ADV_EVT_STACK, skb->data, skb->len);
+ else if (skb->data[0] == 0x13)
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_NOCP_EVT_STACK, skb->data, skb->len);
+ else
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_EVT_STACK, skb->data, skb->len);
+
+ /* dynamic download for connac3 */
+ if (skb->data[0] == 0x0E && skb->data[1] == 0x04 &&
+ skb->data[2] == 0x01 && skb->data[3] == 0x01 &&
+ skb->data[4] == 0xFE) {
+ bdev->fw_bin_info = skb->data[5];
+ /*
+ * Create a thread to do dynamic fwdl
+ * dynamic fwdl will block thread to wait for specific event,
+ * blocking rx_work thread means waited event won't be handled in
+ * rx_work thread, so here we create a new thread for dynamic fwdl.
+ */
+ schedule_work(&bdev->dynamic_fwdl_work);
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s: Get dynamic DL EVENT- ", __func__);
+ /* Drop by driver, don't send to stack */
+ kfree_skb(skb);
+ continue;
+ }
+
+ if (main_info.hif_hook.event_filter(bdev, skb)) {
+ BTMTK_DBG("%s Drop by driver, don't send to stack", __func__);
+ /* Drop by driver, don't send to stack */
+ kfree_skb(skb);
+ continue;
+ }
+ } else if (hci_skb_pkt_type(skb) == HCI_ACLDATA_PKT) {
+ /* save hci acl pkt for debug, not include picus log and coredump*/
+ if (!(skb->data[0] == 0xFF && skb->data[1] == 0xF0))
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_RX_ACL_STACK, skb->data, skb->len);
+
+ } else if (hci_skb_pkt_type(skb) == HCI_ISO_PKT) {
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_RX_ISO_STACK, skb->data, skb->len);
+
+#if (USE_DEVICE_NODE == 1)
+ } else if (hci_skb_pkt_type(skb) == RHW_WRITE_TYPE
+ || hci_skb_pkt_type(skb) == RHW_READ_TYPE) {
+ main_info.hif_hook.event_filter(bdev, skb);
+ BTMTK_DBG("%s Drop by driver, don't send to stack", __func__);
+ /* Drop by driver, don't send to stack */
+ kfree_skb(skb);
+ continue;
+#endif
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ /* BT close case, drop by driver, don't send to stack */
+ kfree_skb(skb);
+ continue;
+ }
+
+ if (is_mt66xx(bdev->chip_id)) {
+ err = btmtk_cif_rx_packet_handler(bdev->hdev, skb);
+ } else {
+ /* for bluetooth kpi */
+ btmtk_dispatch_fwlog_bluetooth_kpi(bdev, skb->data, skb->len, hci_skb_pkt_type(skb));
+ /* If reset stack enabled,
+ * driver should discard the frames
+ * when is in suspend/resume state
+ */
+ state = btmtk_get_chip_state(bdev);
+ if (bdev->bt_cfg.reset_stack_after_woble &&
+ (state == BTMTK_STATE_SUSPEND || state == BTMTK_STATE_RESUME)) {
+ kfree_skb(skb);
+ continue;
+ }
+
+ err = hci_recv_frame(bdev->hdev, skb);
+ }
+ if (err < 0) {
+ if (err != -ENXIO)
+ BTMTK_ERR("%s btmtk_rx_work failed, err = %d", __func__, err);
+ return;
+ }
+ }
+}
+
+void btmtk_free_hci_device(struct btmtk_dev *bdev, int hci_bus_type)
+{
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+
+ if (!bdev)
+ return;
+
+ BTMTK_INFO("%s Begin", __func__);
+ /* Flush RX works */
+ flush_work(&bdev->rx_work);
+
+ if (skb_queue_len(&bdev->rx_q) != 0) {
+ /* Drop queues */
+ skb_queue_purge(&bdev->rx_q);
+ }
+ if (bdev->workqueue) {
+ destroy_workqueue(bdev->workqueue);
+ bdev->workqueue = NULL;
+ }
+
+ if (bdev->hdev) {
+ hci_free_dev(bdev->hdev);
+ bdev->hdev = NULL;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate == BTMTK_FOPS_STATE_OPENED || fstate == BTMTK_FOPS_STATE_CLOSING) {
+ BTMTK_WARN("%s: fstate = %d , set reset_stack_flag", __func__, fstate);
+ if (main_info.reset_stack_flag == HW_ERR_NONE)
+ main_info.reset_stack_flag = HW_ERR_CODE_USB_DISC;
+ }
+
+ bdev->get_hci_reset = 0;
+ BTMTK_INFO("%s End", __func__);
+}
+
+int btmtk_allocate_hci_device(struct btmtk_dev *bdev, int hci_bus_type)
+{
+ struct hci_dev *hdev;
+ int err = 0;
+
+ if (!bdev) {
+ BTMTK_ERR("%s, bdev is NULL!", __func__);
+ err = -EINVAL;
+ goto end;
+ }
+
+ BTMTK_INFO("%s", __func__);
+ /* Add hci device */
+ hdev = hci_alloc_dev();
+ if (!hdev) {
+ BTMTK_ERR("%s, hdev is NULL!", __func__);
+ err = -ENOMEM;
+ goto end;
+ }
+
+ hdev->bus = hci_bus_type;
+ hci_set_drvdata(hdev, bdev);
+
+ /* HCI_PRIMARY = 0x00 */
+ hdev->dev_type = 0x00;
+
+ bdev->hdev = hdev;
+
+ /* register hci callback */
+ hdev->open = bt_open;
+ hdev->close = bt_close;
+ hdev->flush = bt_flush;
+ hdev->send = bt_send_frame;
+ hdev->setup = bt_setup;
+
+ init_waitqueue_head(&bdev->p_wait_event_q);
+
+ /* rx_work init */
+ INIT_WORK(&bdev->rx_work, btmtk_rx_work);
+ INIT_WORK(&bdev->dynamic_fwdl_work, btmtk_dynamic_fwdl_work);
+#if (USE_DEVICE_NODE == 1)
+ INIT_WORK(&bdev->async_trx_work, btmtk_async_trx_work);
+#endif
+ skb_queue_head_init(&bdev->rx_q);
+ bdev->workqueue = alloc_workqueue("BTMTK_RX_WQ", WQ_HIGHPRI | WQ_UNBOUND |
+ WQ_MEM_RECLAIM, 1);
+ if (!bdev->workqueue) {
+ BTMTK_ERR("%s, bdev->workqueue is NULL!", __func__);
+ err = -ENOMEM;
+ goto err0;
+ }
+
+ bdev->get_hci_reset = 0;
+
+ BTMTK_INFO("%s done", __func__);
+ return 0;
+
+err0:
+ hci_free_dev(hdev);
+ hdev = NULL;
+end:
+ return err;
+}
+
+int btmtk_register_hci_device(struct btmtk_dev *bdev)
+{
+ struct hci_dev *hdev;
+ int err = 0;
+ int ret = 0;
+
+ hdev = bdev->hdev;
+
+#if (KERNEL_VERSION(5, 10, 20) < LINUX_VERSION_CODE)
+ set_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks);
+#endif
+
+ err = hci_register_dev(hdev);
+ /* After hci_register_dev completed
+ * It will set dev_flags to HCI_SETUP
+ * That cause vendor_lib create socket failed
+ */
+ if (err < 0) {
+ BTMTK_INFO("%s can't register", __func__);
+ goto exit;
+ }
+
+#if CFG_SUPPORT_BLUEZ
+
+#else
+ /* why need to clear flag HCI_SETUP? */
+ /* reason: if don't clearflag HCI_SETUP, bluedroid do open will return at
+ * the case HCI_CHANNEL_USER of hci_sock_bind API, because hci_dev_test_flag(hdev, HCI_SETUP)
+ * is true, it will goto done, just skip the hci_dev_open
+ */
+
+#if (KERNEL_VERSION(4, 4, 0) > LINUX_VERSION_CODE)
+ ret = test_and_clear_bit(HCI_SETUP, &hdev->dev_flags);
+#else
+ ret = hci_dev_test_and_clear_flag(hdev, HCI_SETUP);
+#endif
+ BTMTK_INFO("%s, the bit value returned is %d", __func__, ret);
+#endif /* CFG_SUPPORT_BLUEZ */
+
+exit:
+ return err;
+}
+
+int btmtk_deregister_hci_device(struct btmtk_dev *bdev)
+{
+ int err = 0;
+
+ /* when not do hci_register_dev action, we do hci_unregister_dev will crash,
+ * so we add test_flag to decide whether hci_register_dev has been
+ * successful or failed, if hci_register_dev success, it will set flag to
+ * HCI_BREDR_ENABLED, After this flag has been set to HCI_BREDR_ENABLED, we
+ * can be able to do hci_unregister_dev.
+ */
+#if (KERNEL_VERSION(4, 1, 0) > LINUX_VERSION_CODE)
+ if (bdev && bdev->hdev && test_bit(HCI_BREDR_ENABLED, &bdev->hdev->dev_flags)) {
+#else
+ if (bdev && bdev->hdev && hci_dev_test_flag(bdev->hdev, HCI_BREDR_ENABLED)) {
+#endif
+ hci_unregister_dev(bdev->hdev);
+ BTMTK_INFO("%s end", __func__);
+ }
+
+ return err;
+}
+
+static int btmtk_main_allocate_memory(struct btmtk_dev *bdev)
+{
+ int err = -1;
+
+ BTMTK_INFO("%s Begin", __func__);
+
+ if (bdev->rom_patch_bin_file_name == NULL) {
+ bdev->rom_patch_bin_file_name = kzalloc(MAX_BIN_FILE_NAME_LEN, GFP_KERNEL);
+ if (!bdev->rom_patch_bin_file_name) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->rom_patch_bin_file_name)", __func__);
+ goto end;
+ }
+ }
+
+ if (bdev->io_buf == NULL) {
+ bdev->io_buf = kzalloc(IO_BUF_SIZE, GFP_KERNEL);
+ if (!bdev->io_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->io_buf)", __func__);
+ goto err2;
+ }
+ }
+
+ if (bdev->bt_cfg_file_name == NULL) {
+ bdev->bt_cfg_file_name = kzalloc(MAX_BIN_FILE_NAME_LEN, GFP_KERNEL);
+ if (!bdev->bt_cfg_file_name) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->bt_cfg_file_name)", __func__);
+ goto err1;
+ }
+ }
+
+ if (bdev->country_file_name == NULL) {
+ bdev->country_file_name = kzalloc(MAX_BIN_FILE_NAME_LEN, GFP_KERNEL);
+ if (!bdev->country_file_name) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->country_file_name)", __func__);
+ goto err0;
+ }
+ }
+ BTMTK_INFO("%s Done", __func__);
+ return 0;
+
+err0:
+ kfree(bdev->bt_cfg_file_name);
+ bdev->bt_cfg_file_name = NULL;
+err1:
+ kfree(bdev->io_buf);
+ bdev->io_buf = NULL;
+err2:
+ kfree(bdev->rom_patch_bin_file_name);
+ bdev->rom_patch_bin_file_name = NULL;
+end:
+ return err;
+}
+
+static void btmtk_main_free_memory(struct btmtk_dev *bdev)
+{
+ kfree(bdev->rom_patch_bin_file_name);
+ bdev->rom_patch_bin_file_name = NULL;
+
+ kfree(bdev->bt_cfg_file_name);
+ bdev->bt_cfg_file_name = NULL;
+
+ kfree(bdev->country_file_name);
+ bdev->country_file_name = NULL;
+
+ kfree(bdev->io_buf);
+ bdev->io_buf = NULL;
+
+ BTMTK_INFO("%s: Success", __func__);
+}
+
+int btmtk_main_cif_initialize(struct btmtk_dev *bdev, int hci_bus)
+{
+ int err = 0;
+
+ btmtk_init_node();
+
+ btmtk_reset_timer_add(bdev);
+
+ err = btmtk_main_allocate_memory(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_main_allocate_memory failed!");
+ goto end;
+ }
+
+ btmtk_initialize_cfg_items(bdev);
+
+ err = btmtk_allocate_hci_device(bdev, hci_bus);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_allocate_hci_device failed!");
+ goto free_mem;
+ }
+
+ err = btmtk_cap_init(bdev);
+ if (err < 0) {
+ if (err == -EIO) {
+ BTMTK_ERR("btmtk_cap_init failed, do chip reset!");
+ goto end;
+ } else {
+ BTMTK_ERR("btmtk_cap_init failed!");
+ goto free_hci_dev;
+ }
+ }
+
+#if (USE_DEVICE_NODE == 0)
+ btmtk_load_bt_cfg(bdev->bt_cfg_file_name, bdev->intf_dev, bdev);
+
+ (void)snprintf(bdev->country_file_name, MAX_BIN_FILE_NAME_LEN,
+ DEFAULT_COUNTRY_TABLE_NAME);
+#endif
+
+#ifdef BTMTK_DEBUG_SOP
+#ifdef DEFAULT_DEBUG_SOP_NAME
+ /* debug sop */
+ snprintf(bdev->debug_sop_file_name, MAX_BIN_FILE_NAME_LEN,
+ "%s_%x.bin", DEFAULT_DEBUG_SOP_NAME, bdev->chip_id & 0xffff);
+ BTMTK_INFO("%s: debug sop file name is %s", __func__,
+ bdev->debug_sop_file_name);
+
+#if (USE_DEVICE_NODE == 0)
+ btmtk_load_debug_sop_register(bdev->debug_sop_file_name, bdev->intf_dev, bdev);
+#endif
+#endif
+#endif
+
+ return 0;
+
+free_hci_dev:
+ btmtk_free_hci_device(bdev, hci_bus);
+free_mem:
+ btmtk_main_free_memory(bdev);
+end:
+ return err;
+}
+
+void btmtk_main_cif_uninitialize(struct btmtk_dev *bdev, int hci_bus)
+{
+ BTMTK_DBG("%s start", __func__);
+ btmtk_free_setting_file(bdev);
+ btmtk_free_hci_device(bdev, hci_bus);
+ btmtk_main_free_memory(bdev);
+ btmtk_reset_timer_del(bdev);
+#ifdef BTMTK_DEBUG_SOP
+ btmtk_clean_debug_reg_file(bdev);
+#endif
+}
+
+int btmtk_main_cif_disconnect_notify(struct btmtk_dev *bdev, int hci_bus)
+{
+ /* need to rewirte when add txqueue, because usb need to add more clear action
+ * when do whole chip reset, usb need to do clear action in usb_close when disconnect,
+ * because usb_close will not execute when do chip reset
+ */
+ BTMTK_DBG("%s: start", __func__);
+ cancel_work_sync(&bdev->rx_work);
+ cancel_work_sync(&bdev->dynamic_fwdl_work);
+#if (USE_DEVICE_NODE == 0)
+ btmtk_deregister_hci_device(bdev);
+#endif
+ btmtk_main_cif_uninitialize(bdev, hci_bus);
+
+#if (USE_DEVICE_NODE == 1)
+ if (main_info.hif_hook.coredump_handler) {
+ BTMTK_INFO("%s: deinit coredump handle", __func__);
+ connv3_coredump_deinit(main_info.hif_hook.coredump_handler);
+ }
+#endif
+
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_OFF;
+ /* btmtk_release_dev(bdev); */
+
+ return 0;
+}
+
+static int btmtk_reboot_notify(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ int ret = 0;
+ int i = 0;
+ int cif_event = 0;
+ unsigned char fstate = 0;
+ int state = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ BTMTK_INFO("%s: btmtk_reboot_notify(%d)", __func__, (int)event);
+
+ if (event == SYS_POWER_OFF && main_info.hif_hook.enter_standby != NULL) {
+ BTMTK_DBG("%s: set woble for standby", __func__);
+ main_info.hif_hook.enter_standby();
+ }
+
+ if (event == SYS_RESTART) {
+ BTMTK_INFO("%s: enter", __func__);
+ for (i = 0; i < btmtk_intf_num; i++) {
+ /* Find valid dev for already probe interface. */
+ if (g_bdev[i]->hdev != NULL) {
+ bdev = g_bdev[i];
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not opened(%d)", __func__, fstate);
+ continue;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_WORKING) {
+ BTMTK_WARN("%s: not in working(%d).", __func__, state);
+ continue;
+ }
+
+ cif_event = HIF_EVENT_DISCONNECT;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s parameter is NULL", __func__);
+ continue;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_CLOSING);
+
+ if (main_info.hif_hook.cif_mutex_lock)
+ main_info.hif_hook.cif_mutex_lock(bdev);
+
+ ret = btmtk_send_deinit_cmds(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s, btmtk_send_deinit_cmds failed", __func__);
+
+ main_info.hif_hook.close(bdev->hdev);
+
+ if (main_info.hif_hook.cif_mutex_unlock)
+ main_info.hif_hook.cif_mutex_unlock(bdev);
+
+ btmtk_fops_set_state(bdev, BTMTK_FOPS_STATE_CLOSED);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static struct notifier_block btmtk_reboot_notifier = {
+ .notifier_call = btmtk_reboot_notify,
+ .next = NULL,
+ .priority = 0,
+};
+
+static int main_init(void)
+{
+ int i = 0;
+
+ BTMTK_INFO("%s", __func__);
+
+ /* Check if user changes default minimum supported intf count */
+ if (btmtk_intf_num < BT_MCU_MINIMUM_INTERFACE_NUM) {
+ btmtk_intf_num = BT_MCU_MINIMUM_INTERFACE_NUM;
+ BTMTK_WARN("%s minimum interface is %d", __func__, btmtk_intf_num);
+ }
+
+ BTMTK_INFO("%s supported intf count <%d>", __func__, btmtk_intf_num);
+
+ BTMTK_INFO("%s: Register reboot_notifier callback success.", __func__);
+ /* Is it necessary? bt_close will be called by reboot. */
+ register_reboot_notifier(&btmtk_reboot_notifier);
+ g_bdev = kzalloc((sizeof(*g_bdev) * btmtk_intf_num), GFP_KERNEL);
+ if (!g_bdev) {
+ BTMTK_WARN("%s insufficient memory", __func__);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < btmtk_intf_num; i++) {
+ g_bdev[i] = btmtk_allocate_dev_memory(NULL);
+ if (g_bdev[i]) {
+ /* BTMTK_STATE_UNKNOWN instead? */
+ /* btmtk_set_chip_state(g_bdev[i], BTMTK_STATE_INIT); */
+
+ /* BTMTK_FOPS_STATE_UNKNOWN instead? */
+ btmtk_fops_set_state(g_bdev[i], BTMTK_FOPS_STATE_INIT);
+ } else {
+ return -ENOMEM;
+ }
+ }
+
+ // Set global variable for btif interface
+ g_sbdev = g_bdev[0];
+
+ btmtk_main_info_initialize();
+
+ return 0;
+}
+
+static int main_exit(void)
+{
+ int i = 0;
+
+ BTMTK_INFO("%s releasing intf count <%d>", __func__, btmtk_intf_num);
+
+ if (g_bdev == NULL) {
+ BTMTK_WARN("%s g_data is NULL", __func__);
+ return 0;
+ }
+
+ BTMTK_INFO("%s: Unregister reboot_notifier callback success.", __func__);
+ /* Is it necessary? bt_close will be called by reboot. */
+ unregister_reboot_notifier(&btmtk_reboot_notifier);
+
+ wakeup_source_unregister(main_info.fwdump_ws);
+ wakeup_source_unregister(main_info.woble_ws);
+ wakeup_source_unregister(main_info.eint_ws);
+#if WAKEUP_BT_IRQ
+ wakeup_source_unregister(main_info.irq_ws);
+#endif
+
+ for (i = 0; i < btmtk_intf_num; i++) {
+ if (g_bdev[i] != NULL)
+ btmtk_free_dev_memory(NULL, g_bdev[i]);
+ }
+
+ kfree(g_bdev);
+ return 0;
+}
+
+/**
+ * Kernel Module init/exit Functions
+ */
+
+int __init main_driver_init(void)
+{
+ int ret = 0;
+ int i;
+
+ /* Mediatek Driver Version */
+ BTMTK_INFO("%s: MTK BT Driver Version : %s", __func__, VERSION);
+
+ ret = main_init();
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < btmtk_intf_num; i++)
+ btmtk_set_chip_state(g_bdev[i], BTMTK_STATE_DISCONNECT);
+
+ ret = btmtk_cif_register();
+ if (ret < 0) {
+ BTMTK_ERR("*** STPBTFWLOG registration failed(%d)! ***", ret);
+ main_exit();
+ return ret;
+ }
+
+ if (main_info.hif_hook.init)
+ ret = main_info.hif_hook.init();
+
+ BTMTK_INFO("%s: Done", __func__);
+ return ret;
+}
+
+void __exit main_driver_exit(void)
+{
+ BTMTK_INFO("%s", __func__);
+
+ btmtk_deinit_node();
+
+ if (main_info.hif_hook.exit)
+ main_info.hif_hook.exit();
+
+ btmtk_cif_deregister();
+
+ main_exit();
+}
+
+module_init(main_driver_init);
+module_exit(main_driver_exit);
+
+/**
+ * Module Common Information
+ */
+MODULE_DESCRIPTION("Mediatek Bluetooth Driver");
+MODULE_VERSION(VERSION SUBVER);
+MODULE_LICENSE("GPL");
+module_param(btmtk_intf_num, int, 0444);
diff --git a/btmtk_queue.c b/btmtk_queue.c
new file mode 100644
index 0000000..d02b332
--- /dev/null
+++ b/btmtk_queue.c
@@ -0,0 +1,646 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+#include <linux/rtc.h>
+
+#include "btmtk_chip_if.h"
+#include "btmtk_main.h"
+#include "btmtk_queue.h"
+
+/*
+ *******************************************************************************
+ * C O N S T A N T
+ *******************************************************************************
+ */
+
+/*
+ *******************************************************************************
+ * D A T A T Y P E S
+ *******************************************************************************
+ */
+
+/*
+ *******************************************************************************
+ * P U B L I C D A T A
+ *******************************************************************************
+ */
+
+
+/*
+ *******************************************************************************
+ * P R I V A T E D A T A
+ *******************************************************************************
+ */
+//extern struct bt_dbg_st g_bt_dbg_st;
+
+static struct bt_ring_buffer_mgmt g_rx_buffer;
+static BT_RX_EVENT_CB g_rx_event_cb;
+
+#if (DRIVER_CMD_CHECK == 1)
+static struct workqueue_struct *workqueue_task;
+static struct delayed_work work;
+#endif
+
+/*
+ *******************************************************************************
+ * F U N C T I O N D E C L A R A T I O N S
+ *******************************************************************************
+ */
+#if (USE_DEVICE_NODE == 1)
+static u8 is_rx_queue_empty(void)
+{
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ spin_lock(&p_ring->lock);
+ if (p_ring->read_idx == p_ring->write_idx) {
+ spin_unlock(&p_ring->lock);
+ return TRUE;
+ }
+
+ spin_unlock(&p_ring->lock);
+ return FALSE;
+}
+
+static u8 is_rx_queue_res_available(u32 length)
+{
+ u32 room_left = 0;
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ /*
+ * Get available space of RX Queue
+ */
+ spin_lock(&p_ring->lock);
+ if (p_ring->read_idx <= p_ring->write_idx)
+ room_left = RING_BUFFER_SIZE - p_ring->write_idx + p_ring->read_idx - 1;
+ else
+ room_left = p_ring->read_idx - p_ring->write_idx - 1;
+ spin_unlock(&p_ring->lock);
+
+ if (room_left < length) {
+ BTMTK_WARN("RX queue room left (%u) < required (%u)", room_left, length);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static s32 rx_pkt_enqueue(u8 *buffer, u32 length)
+{
+ s32 tail_len = 0;
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ if (length > BT_HCI_MAX_FRAME_SIZE) {
+ BTMTK_ERR("Abnormal packet length %u, not enqueue!", length);
+ return -EINVAL;
+ }
+
+ spin_lock(&p_ring->lock);
+ if (p_ring->write_idx + length < RING_BUFFER_SIZE) {
+ memcpy(p_ring->buf + p_ring->write_idx, buffer, length);
+ p_ring->write_idx += length;
+ } else {
+ tail_len = RING_BUFFER_SIZE - p_ring->write_idx;
+ memcpy(p_ring->buf + p_ring->write_idx, buffer, tail_len);
+ memcpy(p_ring->buf, buffer + tail_len, length - tail_len);
+ p_ring->write_idx = length - tail_len;
+ }
+ spin_unlock(&p_ring->lock);
+
+ return 0;
+
+}
+
+s32 rx_skb_enqueue(struct sk_buff *skb)
+{
+ s32 ret = 0;
+
+ if (!skb || skb->len == 0) {
+ BTMTK_WARN("Inavlid data event, skip, skb = NULL or skb len = 0");
+ ret = -1;
+ goto end;
+ }
+
+#if 0
+ #define WAIT_TIMES 40
+
+ /*
+ * FW will block the data if it's buffer is full,
+ * driver can wait a interval for native process to read out
+ */
+ if (g_bt_dbg_st.rx_buf_ctrl == TRUE) {
+ for (i = 0; i < WAIT_TIMES; i++) {
+ if (!is_rx_queue_res_available(skb->len + 1))
+ usleep_range(USLEEP_5MS_L, USLEEP_5MS_H);
+ else
+ break;
+ }
+ }
+#endif
+
+ if (!is_rx_queue_res_available(skb->len + 1)) {
+ BTMTK_WARN("rx packet drop!!!");
+ ret = -1;
+ goto end;
+ }
+
+ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
+ ret = rx_pkt_enqueue(skb->data, skb->len);
+
+ if (!is_rx_queue_empty() && g_rx_event_cb)
+ g_rx_event_cb();
+
+end:
+ if (skb)
+ kfree_skb(skb);
+ return ret;
+}
+
+static void rx_dequeue(struct hci_dev *hdev, u8 *buffer, u32 size, u32 *plen)
+{
+ u32 copy_len = 0, tail_len;
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ spin_lock(&p_ring->lock);
+ if (p_ring->read_idx != p_ring->write_idx) {
+ /*
+ * RX Queue not empty,
+ * fill out the retrieving buffer untill it is full, or we have no data.
+ */
+ if (p_ring->read_idx < p_ring->write_idx) {
+ copy_len = p_ring->write_idx - p_ring->read_idx;
+ if (copy_len > size)
+ copy_len = size;
+ memcpy(buffer, p_ring->buf + p_ring->read_idx, copy_len);
+ p_ring->read_idx += copy_len;
+ } else { /* read_idx > write_idx */
+ tail_len = RING_BUFFER_SIZE - p_ring->read_idx;
+ if (tail_len > size) { /* exclude equal case to skip wrap check */
+ copy_len = size;
+ memcpy(buffer, p_ring->buf + p_ring->read_idx, copy_len);
+ p_ring->read_idx += copy_len;
+ } else {
+ /* 1. copy tail */
+ memcpy(buffer, p_ring->buf + p_ring->read_idx, tail_len);
+ /* 2. check if head length is enough */
+ copy_len = (p_ring->write_idx < (size - tail_len))
+ ? p_ring->write_idx : (size - tail_len);
+ /* 3. copy header */
+ memcpy(buffer + tail_len, p_ring->buf, copy_len);
+ p_ring->read_idx = copy_len;
+ /* 4. update copy length: head + tail */
+ copy_len += tail_len;
+ }
+ }
+ }
+ spin_unlock(&p_ring->lock);
+
+ *plen = copy_len;
+}
+
+static void rx_queue_flush(void)
+{
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ p_ring->read_idx = p_ring->write_idx = 0;
+}
+
+void rx_queue_initialize(void)
+{
+ struct bt_ring_buffer_mgmt *p_ring = &g_rx_buffer;
+
+ p_ring->read_idx = p_ring->write_idx = 0;
+ spin_lock_init(&p_ring->lock);
+}
+
+void rx_queue_destroy(void)
+{
+ rx_queue_flush();
+}
+
+/* Interface for device node mechanism */
+void btmtk_rx_flush(void)
+{
+ rx_queue_flush();
+}
+
+uint8_t btmtk_rx_data_valid(void)
+{
+ return !is_rx_queue_empty();
+}
+
+void btmtk_register_rx_event_cb(BT_RX_EVENT_CB cb)
+{
+ g_rx_event_cb = cb;
+ btmtk_rx_flush();
+}
+
+int32_t btmtk_receive_data(struct hci_dev *hdev, u8 *buf, u32 count)
+{
+ u32 read_bytes = 0;
+
+ rx_dequeue(hdev, buf, count, &read_bytes);
+ //BTMTK_DBG_RAW(buf, read_bytes, "%s, len[%d]", __func__, read_bytes);
+ /* TODO: disable quick PS mode by traffic density */
+ return read_bytes;
+}
+
+int32_t btmtk_send_data(struct hci_dev *hdev, u8 *buf, u32 count)
+{
+ struct sk_buff *skb = NULL;
+
+ if (hdev == NULL) {
+ BTMTK_ERR("%s hdev is NULL", __func__);
+ return -1;
+ }
+
+ if (count <= 0 || buf == NULL) {
+ BTMTK_ERR("%s: error input length (%d) or buffer (%p)", count, buf);
+ return -1;
+ }
+
+ skb = alloc_skb(count + BT_SKB_RESERVE, GFP_KERNEL);
+ if (skb == NULL) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ return -1;
+ }
+
+ /* Reserv for core and drivers use */
+ skb_reserve(skb, BT_SKB_RESERVE);
+ bt_cb(skb)->pkt_type = buf[0];
+ memcpy(skb->data, buf + 1, count - 1);
+ skb->len = count - 1;
+
+ if (hdev->send(hdev, skb) < 0 && skb != NULL) {
+ BTMTK_ERR_LIMITTED("%s send fail, free skb", __func__);
+ kfree_skb(skb);
+ skb = NULL;
+ return -1;
+ }
+
+ return count;
+}
+
+
+#endif // (USE_DEVICE_NODE == 1)
+
+#if (DRIVER_CMD_CHECK == 1)
+
+void cmd_list_initialize(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+
+ BTMTK_DBG("%s", __func__);
+
+ p_queue = &cif_dev->cmd_queue;
+ p_queue->head = NULL;
+ p_queue->tail = NULL;
+ p_queue->size = 0;
+ spin_lock_init(&p_queue->lock);
+}
+
+struct bt_cmd_node *cmd_free_node(struct bt_cmd_node *node)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+ struct bt_cmd_node *next = node->next;
+
+ p_queue = &cif_dev->cmd_queue;
+ kfree(node);
+ p_queue->size--;
+
+ return next;
+}
+
+bool cmd_list_isempty(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+
+ p_queue = &cif_dev->cmd_queue;
+
+ spin_lock(&p_queue->lock);
+ if (p_queue->size == 0) {
+ spin_unlock(&p_queue->lock);
+ return TRUE;
+ }
+
+ spin_unlock(&p_queue->lock);
+ return FALSE;
+}
+
+bool cmd_list_append(uint16_t opcode)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+ struct bt_cmd_node *node = kzalloc(sizeof(struct bt_cmd_node), GFP_KERNEL);
+
+ p_queue = &cif_dev->cmd_queue;
+ if (!node) {
+ BTMTK_ERR("%s create node fail", __func__);
+ return FALSE;
+ }
+ spin_lock(&p_queue->lock);
+ node->next = NULL;
+ node->opcode = opcode;
+
+ if (p_queue->tail == NULL) {
+ p_queue->head = node;
+ p_queue->tail = node;
+ } else {
+ p_queue->tail->next = node;
+ p_queue->tail = node;
+ }
+ p_queue->size++;
+
+ spin_unlock(&p_queue->lock);
+
+ return TRUE;
+}
+
+bool cmd_list_check(uint16_t opcode)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+ struct bt_cmd_node *curr = NULL;
+
+ p_queue = &cif_dev->cmd_queue;
+
+ if (cmd_list_isempty() == TRUE)
+ return FALSE;
+
+ spin_lock(&p_queue->lock);
+ curr = p_queue->head;
+
+ while (curr) {
+ if (curr->opcode == opcode) {
+ spin_unlock(&p_queue->lock);
+ return TRUE;
+ }
+ curr = curr->next;
+ }
+ spin_unlock(&p_queue->lock);
+
+ return FALSE;
+}
+
+bool cmd_list_remove(uint16_t opcode)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+ struct bt_cmd_node *prev = NULL;
+ struct bt_cmd_node *curr = NULL;
+
+ p_queue = &cif_dev->cmd_queue;
+
+ if (cmd_list_isempty() == TRUE)
+ return FALSE;
+
+ spin_lock(&p_queue->lock);
+
+ if (p_queue->head->opcode == opcode) {
+ struct bt_cmd_node *next = cmd_free_node(p_queue->head);
+
+ if (p_queue->head == p_queue->tail)
+ p_queue->tail = NULL;
+
+ p_queue->head = next;
+ spin_unlock(&p_queue->lock);
+ return TRUE;
+ }
+
+ prev = p_queue->head;
+ curr = p_queue->head->next;
+
+ while (curr) {
+ if (curr->opcode == opcode) {
+ prev->next = cmd_free_node(curr);
+ if (p_queue->tail == curr)
+ p_queue->tail = prev;
+
+ spin_unlock(&p_queue->lock);
+ return TRUE;
+ }
+ prev = curr;
+ curr = curr->next;
+ }
+ BTMTK_ERR("%s No match opcode: %4X", __func__, opcode);
+ spin_unlock(&p_queue->lock);
+ return FALSE;
+}
+
+void cmd_list_destory(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+ struct bt_cmd_node *curr = NULL;
+
+ BTMTK_DBG("%s", __func__);
+ p_queue = &cif_dev->cmd_queue;
+ spin_lock(&p_queue->lock);
+ curr = p_queue->head;
+ while (curr)
+ curr = cmd_free_node(curr);
+
+ p_queue->head = NULL;
+ p_queue->tail = NULL;
+ p_queue->size = 0;
+ spin_unlock(&p_queue->lock);
+}
+
+void command_response_timeout(struct work_struct *pwork)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+
+ p_queue = &cif_dev->cmd_queue;
+ if (p_queue->size != 0) {
+ cif_dev->cmd_timeout_count++;
+
+ BTMTK_INFO("[%s] timeout [%d] sleep [%d] force_on [%d]", __func__,
+ cif_dev->cmd_timeout_count,
+ cif_dev->psm.sleep_flag,
+ cif_dev->psm.force_on);
+ btmtk_cif_dump_rxd_backtrace();
+ btmtk_cif_dump_fw_no_rsp(BT_BTIF_DUMP_REG);
+ if (cif_dev->cmd_timeout_count == 4) {
+ spin_lock(&p_queue->lock);
+ if (p_queue->head)
+ BTMTK_ERR("%s, !!!! Command Timeout !!!! opcode 0x%4X",
+ __func__, p_queue->head->opcode);
+ else
+ BTMTK_ERR("%s, p_queue head is NULL", __func__);
+ spin_unlock(&p_queue->lock);
+ // To-do : Need to consider if it has any condition to check
+ cif_dev->cmd_timeout_count = 0;
+ bt_trigger_reset();
+ } else {
+ down(&cif_dev->cmd_tout_sem);
+ if (workqueue_task != NULL)
+ queue_delayed_work(workqueue_task, &work, HZ>>1);
+ up(&cif_dev->cmd_tout_sem);
+ }
+ }
+}
+
+bool cmd_workqueue_init(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+
+ BTMTK_INFO("init workqueue");
+ workqueue_task = create_singlethread_workqueue("workqueue_task");
+ if (!workqueue_task) {
+ BTMTK_ERR("fail to init workqueue");
+ return FALSE;
+ }
+ INIT_DELAYED_WORK(&work, command_response_timeout);
+ cif_dev->cmd_timeout_count = 0;
+ return TRUE;
+}
+
+void update_command_response_workqueue(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_cmd_queue *p_queue = NULL;
+
+ p_queue = &cif_dev->cmd_queue;
+ if (p_queue->size == 0) {
+ BTMTK_DBG("command queue size = 0");
+ cancel_delayed_work(&work);
+ } else {
+ spin_lock(&p_queue->lock);
+ if (p_queue->head)
+ BTMTK_DBG("update new command queue : %4X", p_queue->head->opcode);
+ else
+ BTMTK_ERR("%s, p_queue head is NULL", __func__);
+ spin_unlock(&p_queue->lock);
+ cif_dev->cmd_timeout_count = 0;
+ cancel_delayed_work(&work);
+ down(&cif_dev->cmd_tout_sem);
+ if (workqueue_task != NULL)
+ queue_delayed_work(workqueue_task, &work, HZ>>1);
+
+ up(&cif_dev->cmd_tout_sem);
+ }
+}
+
+void cmd_workqueue_exit(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ int ret_a = 0, ret_b = 0;
+
+ if (workqueue_task != NULL) {
+ ret_b = cancel_delayed_work(&work);
+ flush_workqueue(workqueue_task);
+ ret_a = cancel_delayed_work(&work);
+ BTMTK_INFO("cancel workqueue before[%d] after[%d] flush", ret_b, ret_a);
+ down(&cif_dev->cmd_tout_sem);
+ destroy_workqueue(workqueue_task);
+ workqueue_task = NULL;
+ up(&cif_dev->cmd_tout_sem);
+ }
+}
+
+const char *direction_tostring(enum bt_direction_type direction_type)
+{
+ const char *type[3] = {"NONE", "TX", "RX"};
+
+ return type[direction_type];
+}
+
+void dump_queue_initialize(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_dump_queue *d_queue = NULL;
+
+ BTMTK_INFO("init dumpqueue");
+ d_queue = &cif_dev->dump_queue;
+ d_queue->index = 0;
+ d_queue->full = 0;
+ spin_lock_init(&d_queue->lock);
+ memset(d_queue->queue, 0, MAX_DUMP_QUEUE_SIZE * sizeof(struct bt_dump_packet));
+}
+
+
+void add_dump_packet(const uint8_t *buffer, const uint32_t length, enum bt_direction_type type)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_dump_queue *d_queue = NULL;
+ uint32_t index = 0;
+ struct bt_dump_packet *p_packet = NULL;
+ uint32_t copysize;
+
+ d_queue = &cif_dev->dump_queue;
+ index = d_queue->index;
+ p_packet = &d_queue->queue[index];
+
+ spin_lock(&d_queue->lock);
+ if (length > MAX_DUMP_DATA_SIZE)
+ copysize = MAX_DUMP_DATA_SIZE;
+ else
+ copysize = length;
+
+ ktime_get_real_ts64(&p_packet->time);
+ ktime_get_ts64(&p_packet->kerneltime);
+ memcpy(p_packet->data, buffer, copysize);
+ p_packet->data_length = length;
+ p_packet->direction_type = type;
+
+ d_queue->index = (d_queue->index+1) % MAX_DUMP_QUEUE_SIZE;
+ BTMTK_DBG("index: %d", d_queue->index);
+ if (d_queue->full == FALSE && d_queue->index == 0)
+ d_queue->full = TRUE;
+ spin_unlock(&d_queue->lock);
+}
+
+void print_dump_packet(struct bt_dump_packet *p_packet)
+{
+ int32_t copysize;
+ uint32_t sec, usec, ksec, knsec;
+ struct rtc_time tm;
+
+ sec = p_packet->time.tv_sec;
+ usec = p_packet->time.tv_nsec/1000;
+ ksec = p_packet->kerneltime.tv_sec;
+ knsec = p_packet->kerneltime.tv_nsec;
+
+ rtc_time64_to_tm(sec, &tm);
+
+ if (p_packet->data_length > MAX_DUMP_DATA_SIZE)
+ copysize = MAX_DUMP_DATA_SIZE;
+ else
+ copysize = p_packet->data_length;
+
+ BTMTK_INFO_RAW(p_packet->data, copysize, "Dump: Time:%02d:%02d:%02d.%06u,
+ Kernel Time:%6d.%09u, %s, Size = %3d, Data: ",
+ tm.tm_hour+8, tm.tm_min, tm.tm_sec, usec, ksec, knsec,
+ direction_tostring(p_packet->direction_type),
+ p_packet->data_length);
+}
+
+void show_all_dump_packet(void)
+{
+ struct btmtk_btif_dev *cif_dev = (struct btmtk_btif_dev *)g_sbdev->cif_dev;
+ struct bt_dump_queue *d_queue = NULL;
+ int32_t i, j, showsize;
+ struct bt_dump_packet *p_packet;
+
+ d_queue = &cif_dev->dump_queue;
+
+ spin_lock(&d_queue->lock);
+ if (d_queue->full == TRUE) {
+ showsize = MAX_DUMP_QUEUE_SIZE;
+ for (i = 0, j = d_queue->index; i < showsize; i++, j++) {
+ p_packet = &d_queue->queue[j % MAX_DUMP_QUEUE_SIZE];
+ print_dump_packet(p_packet);
+ }
+ } else {
+ showsize = d_queue->index;
+ for (i = 0; i < showsize; i++) {
+ p_packet = &d_queue->queue[i];
+ print_dump_packet(p_packet);
+ }
+ }
+ spin_unlock(&d_queue->lock);
+}
+#endif // (DRIVER_CMD_CHECK == 1)
diff --git a/btmtk_woble.c b/btmtk_woble.c
new file mode 100644
index 0000000..70681c2
--- /dev/null
+++ b/btmtk_woble.c
@@ -0,0 +1,998 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/input.h>
+#include <linux/pm_wakeup.h>
+#include <linux/interrupt.h>
+
+#include "btmtk_woble.h"
+
+static int is_support_unify_woble(struct btmtk_dev *bdev)
+{
+ if (bdev->bt_cfg.support_unify_woble) {
+ if (is_mt7902(bdev->chip_id) || is_mt7922(bdev->chip_id) ||
+ is_mt6639(bdev->chip_id) || is_mt7961(bdev->chip_id))
+ return 1;
+ else
+ return 0;
+ } else {
+ return 0;
+ }
+}
+
+static void btmtk_woble_wake_lock(struct btmtk_dev *bdev)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (bdev->bt_cfg.support_woble_wakelock) {
+ BTMTK_INFO("%s: enter", __func__);
+ __pm_stay_awake(bmain_info->woble_ws);
+ BTMTK_INFO("%s: exit", __func__);
+ }
+}
+
+void btmtk_woble_wake_unlock(struct btmtk_dev *bdev)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (bdev->bt_cfg.support_woble_wakelock) {
+ BTMTK_INFO("%s: enter", __func__);
+ __pm_relax(bmain_info->woble_ws);
+ BTMTK_INFO("%s: exit", __func__);
+ }
+}
+
+#if WAKEUP_BT_IRQ
+void btmtk_sdio_irq_wake_lock_timeout(struct btmtk_dev *bdev)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: enter", __func__);
+ __pm_wakeup_event(bmain_info->irq_ws, WAIT_POWERKEY_TIMEOUT);
+ BTMTK_INFO("%s: exit", __func__);
+}
+#endif
+
+
+int btmtk_send_apcf_reserved(struct btmtk_dev *bdev)
+{
+ u8 reserve_apcf_cmd[RES_APCF_CMD_LEN] = { 0x01, 0xC9, 0xFC, 0x05, 0x01, 0x30, 0x02, 0x61, 0x02 };
+ u8 reserve_apcf_event[RES_APCF_EVT_LEN] = { 0x04, 0xE6, 0x02, 0x08, 0x11 };
+ int ret = 0;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: Incorrect bdev", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ if (is_support_unify_woble(bdev)) {
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id))
+ ret = btmtk_main_send_cmd(bdev, reserve_apcf_cmd, RES_APCF_CMD_LEN,
+ reserve_apcf_event, RES_APCF_EVT_LEN, 0, 0,
+ BTMTK_TX_PKT_FROM_HOST);
+ else
+ BTMTK_WARN("%s: not support for 0x%x", __func__, bdev->chip_id);
+
+ BTMTK_INFO("%s: ret %d", __func__, ret);
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_send_woble_read_BDADDR_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[READ_ADDRESS_CMD_LEN] = { 0x01, 0x09, 0x10, 0x00 };
+ u8 event[READ_ADDRESS_EVT_HDR_LEN] = { 0x04, 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00, /* AA, BB, CC, DD, EE, FF */ };
+ int i;
+ int ret = -1;
+
+ BTMTK_INFO("%s: begin", __func__);
+ if (bdev == NULL || bdev->io_buf == NULL) {
+ BTMTK_ERR("%s: Incorrect bdev", __func__);
+ return ret;
+ }
+
+ for (i = 0; i < BD_ADDRESS_SIZE; i++) {
+ if (bdev->bdaddr[i] != 0) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, READ_ADDRESS_CMD_LEN,
+ event, READ_ADDRESS_EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ /*BD address will get in btmtk_rx_work*/
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+done:
+ BTMTK_INFO("%s, end, ret = %d", __func__, ret);
+ return ret;
+}
+
+
+static int btmtk_send_unify_woble_suspend_default_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[WOBLE_ENABLE_DEFAULT_CMD_LEN] = { 0x01, 0xC9, 0xFC, 0x24, 0x01, 0x20, 0x02, 0x00, 0x01,
+ 0x02, 0x01, 0x00, 0x05, 0x10, 0x00, 0x00, 0x40, 0x06,
+ 0x02, 0x40, 0x0A, 0x02, 0x41, 0x0F, 0x05, 0x24, 0x20,
+ 0x04, 0x32, 0x00, 0x09, 0x26, 0xC0, 0x12, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00};
+ u8 event[WOBLE_ENABLE_DEFAULT_EVT_LEN] = { 0x04, 0xE6, 0x02, 0x08, 0x00 };
+ int ret = 0; /* if successful, 0 */
+
+ BTMTK_INFO("%s: begin", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, WOBLE_ENABLE_DEFAULT_CMD_LEN,
+ event, WOBLE_ENABLE_DEFAULT_EVT_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ BTMTK_INFO("%s: end. ret = %d", __func__, ret);
+ return ret;
+}
+
+
+static int btmtk_send_unify_woble_resume_default_cmd(struct btmtk_dev *bdev)
+{
+ u8 cmd[WOBLE_DISABLE_DEFAULT_CMD_LEN] = { 0x01, 0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00 };
+ u8 event[WOBLE_DISABLE_DEFAULT_EVT_LEN] = { 0x04, 0xE6, 0x02, 0x08, 0x01 };
+ int ret = 0; /* if successful, 0 */
+
+ BTMTK_INFO("%s: begin", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, WOBLE_DISABLE_DEFAULT_CMD_LEN,
+ event, WOBLE_DISABLE_DEFAULT_EVT_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ BTMTK_INFO("%s: end. ret = %d", __func__, ret);
+ return ret;
+}
+
+
+static int btmtk_send_woble_suspend_cmd(struct btmtk_dev *bdev)
+{
+ /* radio off cmd with wobx_mode_disable, used when unify woble off */
+ u8 radio_off_cmd[RADIO_OFF_CMD_LEN] = { 0x01, 0xC9, 0xFC, 0x05, 0x01, 0x20, 0x02, 0x00, 0x00 };
+ u8 event[RADIO_OFF_EVT_LEN] = { 0x04, 0xE6, 0x02, 0x08, 0x00 };
+ int ret = 0; /* if successful, 0 */
+
+ BTMTK_INFO("%s: not support woble, send radio off cmd", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ radio_off_cmd, RADIO_OFF_CMD_LEN,
+ event, RADIO_OFF_EVT_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ return ret;
+}
+
+static int btmtk_send_woble_resume_cmd(struct btmtk_dev *bdev)
+{
+ /* radio on cmd with wobx_mode_disable, used when unify woble off */
+ u8 radio_on_cmd[RADIO_ON_CMD_LEN] = { 0x01, 0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00 };
+ u8 event[RADIO_ON_EVT_LEN] = { 0x04, 0xE6, 0x02, 0x08, 0x01 };
+ int ret = 0; /* if successful, 0 */
+
+ BTMTK_INFO("%s: begin", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ radio_on_cmd, RADIO_ON_CMD_LEN,
+ event, RADIO_ON_EVT_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ return ret;
+}
+
+static int btmtk_set_Woble_APCF_filter_parameter(struct btmtk_dev *bdev)
+{
+ u8 cmd[APCF_FILTER_CMD_LEN] = { 0x01, 0x57, 0xFD, 0x0A,
+ 0x01, 0x00, 0x0A, 0x20, 0x00, 0x20, 0x00, 0x01, 0x80, 0x00 };
+ u8 event[APCF_FILTER_EVT_HDR_LEN] = { 0x04, 0x0E, 0x07,
+ 0x01, 0x57, 0xFD, 0x00, 0x01/*, 00, 63*/ };
+ int ret = -1;
+
+ BTMTK_INFO("%s: begin", __func__);
+ ret = btmtk_main_send_cmd(bdev, cmd, APCF_FILTER_CMD_LEN,
+ event, APCF_FILTER_EVT_HDR_LEN, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: end ret %d", __func__, ret);
+ else
+ ret = 0;
+
+ BTMTK_INFO("%s: end ret=%d", __func__, ret);
+ return ret;
+}
+
+/**
+ * Set APCF manufacturer data and filter parameter
+ */
+static int btmtk_set_Woble_APCF(struct btmtk_woble *bt_woble)
+{
+ u8 manufactur_data[APCF_CMD_LEN] = { 0x01, 0x57, 0xFD, 0x27, 0x06, 0x00, 0x0A,
+ 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x4B, 0x54, 0x4D,
+ 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ u8 event[APCF_EVT_HDR_LEN] = { 0x04, 0x0E, 0x07, 0x01, 0x57, 0xFD, 0x00, /* 0x06 00 63 */ };
+ int ret = -1;
+ u8 i = 0;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ BTMTK_INFO("%s: woble_setting_apcf[0].length %d",
+ __func__, bt_woble->woble_setting_apcf[0].length);
+
+ /* start to send apcf cmd from woble setting file */
+ if (bt_woble->woble_setting_apcf[0].length) {
+ for (i = 0; i < WOBLE_SETTING_COUNT; i++) {
+ if (!bt_woble->woble_setting_apcf[i].length)
+ continue;
+
+ BTMTK_INFO("%s: apcf_fill_mac[%d].content[0] = 0x%02x", __func__, i,
+ bt_woble->woble_setting_apcf_fill_mac[i].content[0]);
+ BTMTK_INFO("%s: apcf_fill_mac_location[%d].length = %d", __func__, i,
+ bt_woble->woble_setting_apcf_fill_mac_location[i].length);
+
+ if ((bt_woble->woble_setting_apcf_fill_mac[i].content[0] == 1) &&
+ bt_woble->woble_setting_apcf_fill_mac_location[i].length) {
+ /* need add BD addr to apcf cmd */
+ memcpy(bt_woble->woble_setting_apcf[i].content +
+ (*bt_woble->woble_setting_apcf_fill_mac_location[i].content + 1),
+ bdev->bdaddr, BD_ADDRESS_SIZE);
+ BTMTK_INFO("%s: apcf[%d], add local BDADDR to location %d", __func__, i,
+ (*bt_woble->woble_setting_apcf_fill_mac_location[i].content));
+ }
+#if CFG_SHOW_FULL_MACADDR
+ BTMTK_INFO_RAW(bt_woble->woble_setting_apcf[i].content, bt_woble->woble_setting_apcf[i].length,
+ "Send woble_setting_apcf[%d] ", i);
+#endif
+ ret = btmtk_main_send_cmd(bdev, bt_woble->woble_setting_apcf[i].content,
+ bt_woble->woble_setting_apcf[i].length, event, APCF_EVT_HDR_LEN, 0, 0,
+ BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s: manufactur_data error ret %d", __func__, ret);
+ return ret;
+ }
+ }
+ } else { /* use default */
+ BTMTK_INFO("%s: use default manufactur data", __func__);
+ memcpy(manufactur_data + 10, bdev->bdaddr, BD_ADDRESS_SIZE);
+ ret = btmtk_main_send_cmd(bdev, manufactur_data, APCF_CMD_LEN,
+ event, APCF_EVT_HDR_LEN, 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s: manufactur_data error ret %d", __func__, ret);
+ return ret;
+ }
+
+ ret = btmtk_set_Woble_APCF_filter_parameter(bdev);
+ }
+
+ BTMTK_INFO("%s: end ret=%d", __func__, ret);
+ return 0;
+}
+
+static int btmtk_set_Woble_Radio_Off(struct btmtk_woble *bt_woble)
+{
+ int ret = -1;
+ int length = 0;
+ char *radio_off = NULL;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ BTMTK_INFO("%s: woble_setting_radio_off.length %d", __func__,
+ bt_woble->woble_setting_radio_off.length);
+ if (bt_woble->woble_setting_radio_off.length) {
+ /* start to send radio off cmd from woble setting file */
+ length = bt_woble->woble_setting_radio_off.length +
+ bt_woble->woble_setting_wakeup_type.length;
+ radio_off = kzalloc(length, GFP_KERNEL);
+ if (!radio_off) {
+ BTMTK_ERR("%s: alloc memory fail (radio_off)",
+ __func__);
+ ret = -ENOMEM;
+ goto Finish;
+ }
+
+ memcpy(radio_off,
+ bt_woble->woble_setting_radio_off.content,
+ bt_woble->woble_setting_radio_off.length);
+ if (bt_woble->woble_setting_wakeup_type.length) {
+ memcpy(radio_off + bt_woble->woble_setting_radio_off.length,
+ bt_woble->woble_setting_wakeup_type.content,
+ bt_woble->woble_setting_wakeup_type.length);
+ radio_off[3] += bt_woble->woble_setting_wakeup_type.length;
+ }
+
+ BTMTK_INFO_RAW(radio_off, length, "Send radio off");
+ ret = btmtk_main_send_cmd(bdev, radio_off, length,
+ bt_woble->woble_setting_radio_off_comp_event.content,
+ bt_woble->woble_setting_radio_off_comp_event.length, 0, 0,
+ BTMTK_TX_PKT_FROM_HOST);
+
+ kfree(radio_off);
+ radio_off = NULL;
+ } else { /* use default */
+ BTMTK_INFO("%s: use default radio off cmd", __func__);
+ ret = btmtk_send_unify_woble_suspend_default_cmd(bdev);
+ }
+
+Finish:
+ BTMTK_INFO("%s, end ret=%d", __func__, ret);
+ return ret;
+}
+
+static int btmtk_set_Woble_Radio_On(struct btmtk_woble *bt_woble)
+{
+ int ret = -1;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ BTMTK_INFO("%s: woble_setting_radio_on.length %d", __func__,
+ bt_woble->woble_setting_radio_on.length);
+ if (bt_woble->woble_setting_radio_on.length) {
+ /* start to send radio on cmd from woble setting file */
+ BTMTK_INFO_RAW(bt_woble->woble_setting_radio_on.content,
+ bt_woble->woble_setting_radio_on.length, "send radio on");
+
+ ret = btmtk_main_send_cmd(bdev, bt_woble->woble_setting_radio_on.content,
+ bt_woble->woble_setting_radio_on.length,
+ bt_woble->woble_setting_radio_on_comp_event.content,
+ bt_woble->woble_setting_radio_on_comp_event.length, 0, 0,
+ BTMTK_TX_PKT_FROM_HOST);
+ } else { /* use default */
+ BTMTK_WARN("%s: use default radio on cmd", __func__);
+ ret = btmtk_send_unify_woble_resume_default_cmd(bdev);
+ }
+
+ BTMTK_INFO("%s, end ret=%d", __func__, ret);
+ return ret;
+}
+
+static int btmtk_del_Woble_APCF_index(struct btmtk_dev *bdev)
+{
+ u8 cmd[APCF_DELETE_CMD_LEN] = { 0x01, 0x57, 0xFD, 0x03, 0x01, 0x01, 0x0A };
+ u8 event[APCF_DELETE_EVT_HDR_LEN] = { 0x04, 0x0e, 0x07, 0x01, 0x57, 0xfd, 0x00, 0x01, /* 00, 63 */ };
+ int ret = -1;
+
+ BTMTK_INFO("%s, enter", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, APCF_DELETE_CMD_LEN,
+ event, APCF_DELETE_EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: got error %d", __func__, ret);
+
+ BTMTK_INFO("%s, end", __func__);
+ return ret;
+}
+
+
+static int btmtk_set_Woble_APCF_Resume(struct btmtk_woble *bt_woble)
+{
+ u8 event[APCF_RESUME_EVT_HDR_LEN] = { 0x04, 0x0e, 0x07, 0x01, 0x57, 0xfd, 0x00 };
+ u8 i = 0;
+ int ret = -1;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ BTMTK_INFO("%s, enter, bt_woble->woble_setting_apcf_resume[0].length= %d",
+ __func__, bt_woble->woble_setting_apcf_resume[0].length);
+ if (bt_woble->woble_setting_apcf_resume[0].length) {
+ BTMTK_INFO("%s: handle leave woble apcf from file", __func__);
+ for (i = 0; i < WOBLE_SETTING_COUNT; i++) {
+ if (!bt_woble->woble_setting_apcf_resume[i].length)
+ continue;
+
+ BTMTK_INFO_RAW(bt_woble->woble_setting_apcf_resume[i].content,
+ bt_woble->woble_setting_apcf_resume[i].length,
+ "%s: send apcf resume %d:", __func__, i);
+
+ ret = btmtk_main_send_cmd(bdev,
+ bt_woble->woble_setting_apcf_resume[i].content,
+ bt_woble->woble_setting_apcf_resume[i].length,
+ event, APCF_RESUME_EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0) {
+ BTMTK_ERR("%s: Send apcf resume fail %d", __func__, ret);
+ return ret;
+ }
+ }
+ } else { /* use default */
+ BTMTK_WARN("%s: use default apcf resume cmd", __func__);
+ ret = btmtk_del_Woble_APCF_index(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_del_Woble_APCF_index return fail %d", __func__, ret);
+ }
+ BTMTK_INFO("%s, end", __func__);
+ return ret;
+}
+
+
+static int btmtk_load_woble_setting(char *bin_name,
+ struct device *dev, u32 *code_len, struct btmtk_woble *bt_woble)
+{
+ int err;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ *code_len = 0;
+
+ err = btmtk_load_code_from_setting_files(bin_name, dev, code_len, bdev);
+ if (err) {
+ BTMTK_ERR("woble_setting btmtk_load_code_from_setting_files failed!!");
+ goto LOAD_END;
+ }
+
+ err = btmtk_load_fw_cfg_setting("APCF",
+ bt_woble->woble_setting_apcf, WOBLE_SETTING_COUNT, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("APCF_ADD_MAC",
+ bt_woble->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("APCF_ADD_MAC_LOCATION",
+ bt_woble->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("RADIOOFF", &bt_woble->woble_setting_radio_off, 1,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ switch (bdev->bt_cfg.unify_woble_type) {
+ case 0:
+ err = btmtk_load_fw_cfg_setting("WAKEUP_TYPE_LEGACY", &bt_woble->woble_setting_wakeup_type, 1,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 1:
+ err = btmtk_load_fw_cfg_setting("WAKEUP_TYPE_WAVEFORM", &bt_woble->woble_setting_wakeup_type, 1,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 2:
+ err = btmtk_load_fw_cfg_setting("WAKEUP_TYPE_IR", &bt_woble->woble_setting_wakeup_type, 1,
+ bdev->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ default:
+ BTMTK_WARN("%s: unify_woble_type unknown(%d)", __func__, bdev->bt_cfg.unify_woble_type);
+ }
+ if (err)
+ BTMTK_WARN("%s: Parse unify_woble_type(%d) failed", __func__, bdev->bt_cfg.unify_woble_type);
+
+ err = btmtk_load_fw_cfg_setting("RADIOOFF_STATUS_EVENT",
+ &bt_woble->woble_setting_radio_off_status_event, 1, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("RADIOOFF_COMPLETE_EVENT",
+ &bt_woble->woble_setting_radio_off_comp_event, 1, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("RADIOON",
+ &bt_woble->woble_setting_radio_on, 1, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("RADIOON_STATUS_EVENT",
+ &bt_woble->woble_setting_radio_on_status_event, 1, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("RADIOON_COMPLETE_EVENT",
+ &bt_woble->woble_setting_radio_on_comp_event, 1, bdev->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_END;
+
+ err = btmtk_load_fw_cfg_setting("APCF_RESUME",
+ bt_woble->woble_setting_apcf_resume, WOBLE_SETTING_COUNT, bdev->setting_file, FW_CFG_INX_LEN_2);
+
+LOAD_END:
+ /* release setting file memory */
+ if (bdev) {
+ kfree(bdev->setting_file);
+ bdev->setting_file = NULL;
+ }
+
+ if (err)
+ BTMTK_ERR("%s: error return %d", __func__, err);
+
+ return err;
+}
+
+static void btmtk_check_wobx_debug_log(struct btmtk_dev *bdev)
+{
+ /* 0xFF, 0xFF, 0xFF, 0xFF is log level */
+ u8 cmd[CHECK_WOBX_DEBUG_CMD_LEN] = { 0X01, 0xCE, 0xFC, 0x04, 0xFF, 0xFF, 0xFF, 0xFF };
+ u8 event[CHECK_WOBX_DEBUG_EVT_HDR_LEN] = { 0x04, 0xE8 };
+ int ret = -1;
+
+ BTMTK_INFO("%s: begin", __func__);
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, CHECK_WOBX_DEBUG_CMD_LEN,
+ event, CHECK_WOBX_DEBUG_EVT_HDR_LEN,
+ 0, 0, BTMTK_TX_PKT_FROM_HOST);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ /* Driver just print event to kernel log in rx_work,
+ * Please reference wiki to know what it is.
+ */
+}
+
+
+static int btmtk_handle_leaving_WoBLE_state(struct btmtk_woble *bt_woble)
+{
+ int ret = -1;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: begin", __func__);
+
+#if WAKEUP_BT_IRQ
+ /* Can't enter woble mode */
+ BTMTK_INFO("not support woble mode for wakeup bt irq");
+ return 0;
+#endif
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (!bdev->bt_cfg.support_woble_for_bt_disable) {
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not opened, return", __func__);
+ return 0;
+ }
+ }
+
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open yet(%d), need to start traffic before leaving woble",
+ __func__, fstate);
+ /* start traffic to recv event*/
+ ret = bmain_info->hif_hook.open(bdev->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_open failed", __func__);
+ goto Finish;
+ }
+ }
+
+ if (is_support_unify_woble(bdev)) {
+ ret = btmtk_set_Woble_Radio_On(bt_woble);
+ if (ret < 0)
+ goto Finish;
+
+ ret = btmtk_set_Woble_APCF_Resume(bt_woble);
+ if (ret < 0)
+ goto Finish;
+ } else {
+ /* radio on cmd with wobx_mode_disable, used when unify woble off */
+ ret = btmtk_send_woble_resume_cmd(bdev);
+ }
+
+Finish:
+ if (ret < 0) {
+ BTMTK_INFO("%s: woble_resume_fail!!!", __func__);
+ } else {
+ /* It's wobx debug log method. */
+ btmtk_check_wobx_debug_log(bdev);
+
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ ret = btmtk_send_deinit_cmds(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, btmtk_send_deinit_cmds failed", __func__);
+ goto exit;
+ }
+
+ BTMTK_WARN("%s: fops is not open(%d), need to stop traffic after leaving woble",
+ __func__, fstate);
+ /* stop traffic to stop recv data from fw*/
+ ret = bmain_info->hif_hook.close(bdev->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_close failed", __func__);
+ goto exit;
+ }
+ } else
+ bdev->power_state = BTMTK_DONGLE_STATE_POWER_ON;
+ BTMTK_INFO("%s: success", __func__);
+ }
+
+exit:
+ BTMTK_INFO("%s: end", __func__);
+ return ret;
+}
+
+static int btmtk_handle_entering_WoBLE_state(struct btmtk_woble *bt_woble)
+{
+ int ret = -1;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ int state = BTMTK_STATE_INIT;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: begin", __func__);
+#if WAKEUP_BT_IRQ
+ /* Can't enter woble mode */
+ BTMTK_INFO("not support woble mode for wakeup bt irq");
+ return 0;
+#endif
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (!bdev->bt_cfg.support_woble_for_bt_disable) {
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open yet(%d)!, return", __func__, fstate);
+ return 0;
+ }
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't send any cmd to FW!!!", __func__);
+ goto Finish;
+ }
+
+ if (atomic_read(&bmain_info->chip_reset) || atomic_read(&bmain_info->subsys_reset)) {
+ BTMTK_ERR("%s chip_reset is %d, subsys_reset is %d", __func__,
+ atomic_read(&bmain_info->chip_reset), atomic_read(&bmain_info->subsys_reset));
+ goto Finish;
+ }
+
+ /* Power on first if state is power off */
+ ret = btmtk_reset_power_on(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s: reset power_on fail return", __func__);
+ goto Finish;
+ }
+
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open yet(%d), need to start traffic before enter woble",
+ __func__, fstate);
+ /* start traffic to recv event*/
+ ret = bmain_info->hif_hook.open(bdev->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_open failed", __func__);
+ goto Finish;
+ }
+ }
+ if (is_support_unify_woble(bdev)) {
+ do {
+ typedef ssize_t (*func) (u16 u16Key, const char *buf, size_t size);
+ char *func_name = "MDrv_PM_Write_Key";
+ func pFunc = NULL;
+ ssize_t sret = 0;
+ u8 buf = 0;
+
+ pFunc = (func) btmtk_kallsyms_lookup_name(func_name);
+ if (pFunc && bdev->bt_cfg.unify_woble_type == 1) {
+ buf = 1;
+ sret = pFunc(PM_KEY_BTW, &buf, sizeof(u8));
+ BTMTK_INFO("%s: Invoke %s, buf = %d, sret = %zd", __func__,
+ func_name, buf, sret);
+
+ } else {
+ BTMTK_WARN("%s: No Exported Func Found [%s]", __func__, func_name);
+ }
+ } while (0);
+
+ ret = btmtk_send_woble_read_BDADDR_cmd(bdev);
+ if (ret < 0)
+ goto STOP_TRAFFIC;
+
+ ret = btmtk_set_Woble_APCF(bt_woble);
+ if (ret < 0)
+ goto STOP_TRAFFIC;
+
+ ret = btmtk_set_Woble_Radio_Off(bt_woble);
+ if (ret < 0)
+ goto STOP_TRAFFIC;
+ } else {
+ /* radio off cmd with wobx_mode_disable, used when unify woble off */
+ ret = btmtk_send_woble_suspend_cmd(bdev);
+ }
+
+STOP_TRAFFIC:
+ if (fstate != BTMTK_FOPS_STATE_OPENED) {
+ BTMTK_WARN("%s: fops is not open(%d), need to stop traffic after enter woble",
+ __func__, fstate);
+ /* stop traffic to stop recv data from fw*/
+ ret = bmain_info->hif_hook.close(bdev->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s, cif_close failed", __func__);
+ goto Finish;
+ }
+ }
+
+Finish:
+ if (ret) {
+ bdev->power_state = BTMTK_DONGLE_STATE_ERROR;
+ btmtk_woble_wake_lock(bdev);
+ }
+
+ BTMTK_INFO("%s: end ret = %d, power_state =%d", __func__, ret, bdev->power_state);
+ return ret;
+}
+
+int btmtk_woble_suspend(struct btmtk_woble *bt_woble)
+{
+ int ret = 0;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ BTMTK_INFO("%s: enter", __func__);
+
+ fstate = btmtk_fops_get_state(bdev);
+
+ if (!is_support_unify_woble(bdev) && (fstate != BTMTK_FOPS_STATE_OPENED)) {
+ BTMTK_WARN("%s: when not support woble, in bt off state, do nothing!", __func__);
+ goto exit;
+ }
+
+ ret = btmtk_handle_entering_WoBLE_state(bt_woble);
+ if (ret)
+ BTMTK_ERR("%s: btmtk_handle_entering_WoBLE_state return fail %d", __func__, ret);
+
+exit:
+ BTMTK_INFO("%s: end", __func__);
+ return ret;
+}
+
+int btmtk_woble_resume(struct btmtk_woble *bt_woble)
+{
+ int ret = 0;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = bt_woble->bdev;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_INFO("%s: enter", __func__);
+ fstate = btmtk_fops_get_state(bdev);
+
+ if (!is_support_unify_woble(bdev) && (fstate != BTMTK_FOPS_STATE_OPENED)) {
+ BTMTK_WARN("%s: when not support woble, in bt off state, do nothing!", __func__);
+ goto exit;
+ }
+
+ if (bdev->power_state == BTMTK_DONGLE_STATE_ERROR) {
+ BTMTK_INFO("%s: In BTMTK_DONGLE_STATE_ERROR(Could suspend caused), do assert", __func__);
+ btmtk_send_assert_cmd(bdev);
+ ret = -EBADFD;
+ goto exit;
+ }
+
+ ret = btmtk_handle_leaving_WoBLE_state(bt_woble);
+ if (ret < 0) {
+ BTMTK_ERR("%s: btmtk_handle_leaving_WoBLE_state return fail %d", __func__, ret);
+ /* avoid rtc to to suspend again, do FW dump first */
+ btmtk_woble_wake_lock(bdev);
+ goto exit;
+ }
+
+ if (bdev->bt_cfg.reset_stack_after_woble
+ && bmain_info->reset_stack_flag == HW_ERR_NONE
+ && fstate == BTMTK_FOPS_STATE_OPENED)
+ bmain_info->reset_stack_flag = HW_ERR_CODE_RESET_STACK_AFTER_WOBLE;
+
+ btmtk_send_hw_err_to_host(bdev);
+ BTMTK_INFO("%s: end(%d), reset_stack_flag = %d, fstate = %d", __func__, ret,
+ bmain_info->reset_stack_flag, fstate);
+
+exit:
+ BTMTK_INFO("%s: end", __func__);
+ return ret;
+}
+
+static irqreturn_t btmtk_woble_isr(int irq, struct btmtk_woble *bt_woble)
+{
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ BTMTK_DBG("%s begin", __func__);
+ disable_irq_nosync(bt_woble->wobt_irq);
+ atomic_dec(&(bt_woble->irq_enable_count));
+ BTMTK_INFO("disable BT IRQ, call wake lock");
+ __pm_wakeup_event(bmain_info->eint_ws, WAIT_POWERKEY_TIMEOUT);
+
+ input_report_key(bt_woble->WoBLEInputDev, KEY_WAKEUP, 1);
+ input_sync(bt_woble->WoBLEInputDev);
+ input_report_key(bt_woble->WoBLEInputDev, KEY_WAKEUP, 0);
+ input_sync(bt_woble->WoBLEInputDev);
+ BTMTK_DBG("%s end", __func__);
+ return IRQ_HANDLED;
+}
+
+static int btmtk_RegisterBTIrq(struct btmtk_woble *bt_woble)
+{
+ struct device_node *eint_node = NULL;
+ int interrupts[2];
+
+ BTMTK_DBG("%s begin", __func__);
+ eint_node = of_find_compatible_node(NULL, NULL, "mediatek,woble_eint");
+ if (eint_node) {
+ BTMTK_INFO("Get woble_eint compatible node");
+ bt_woble->wobt_irq = irq_of_parse_and_map(eint_node, 0);
+ BTMTK_INFO("woble_irq number:%d", bt_woble->wobt_irq);
+ if (bt_woble->wobt_irq) {
+ of_property_read_u32_array(eint_node, "interrupts",
+ interrupts, ARRAY_SIZE(interrupts));
+ bt_woble->wobt_irqlevel = interrupts[1];
+ if (request_irq(bt_woble->wobt_irq, (void *)btmtk_woble_isr,
+ bt_woble->wobt_irqlevel, "woble-eint", bt_woble))
+ BTMTK_INFO("WOBTIRQ LINE NOT AVAILABLE!!");
+ else {
+ BTMTK_INFO("disable BT IRQ");
+ disable_irq_nosync(bt_woble->wobt_irq);
+ }
+
+ } else
+ BTMTK_INFO("can't find woble_eint irq");
+
+ } else {
+ bt_woble->wobt_irq = 0;
+ BTMTK_INFO("can't find woble_eint compatible node");
+ }
+
+ BTMTK_DBG("%s end", __func__);
+ return 0;
+}
+
+static int btmtk_woble_input_init(struct btmtk_woble *bt_woble)
+{
+ int ret = 0;
+
+ bt_woble->WoBLEInputDev = input_allocate_device();
+ if (!bt_woble->WoBLEInputDev || IS_ERR(bt_woble->WoBLEInputDev)) {
+ BTMTK_ERR("input_allocate_device error");
+ return -ENOMEM;
+ }
+
+ bt_woble->WoBLEInputDev->name = "WOBLE_INPUT_DEVICE";
+ bt_woble->WoBLEInputDev->id.bustype = BUS_HOST;
+ bt_woble->WoBLEInputDev->id.vendor = 0x0002;
+ bt_woble->WoBLEInputDev->id.product = 0x0002;
+ bt_woble->WoBLEInputDev->id.version = 0x0002;
+
+ __set_bit(EV_KEY, bt_woble->WoBLEInputDev->evbit);
+ __set_bit(KEY_WAKEUP, bt_woble->WoBLEInputDev->keybit);
+
+ ret = input_register_device(bt_woble->WoBLEInputDev);
+ if (ret < 0) {
+ input_free_device(bt_woble->WoBLEInputDev);
+ BTMTK_ERR("input_register_device %d", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void btmtk_woble_input_deinit(struct btmtk_woble *bt_woble)
+{
+ if (bt_woble->WoBLEInputDev) {
+ input_unregister_device(bt_woble->WoBLEInputDev);
+ /* Do not need to free WOBLE_INPUT_DEVICE, because after unregister it,
+ * kernel will free it by itself.
+ */
+ /* input_free_device(bt_woble->WoBLEInputDev); */
+ bt_woble->WoBLEInputDev = NULL;
+ }
+}
+
+static void btmtk_free_woble_setting_file(struct btmtk_woble *bt_woble)
+{
+ btmtk_free_fw_cfg_struct(bt_woble->woble_setting_apcf, WOBLE_SETTING_COUNT);
+ btmtk_free_fw_cfg_struct(bt_woble->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT);
+ btmtk_free_fw_cfg_struct(bt_woble->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT);
+ btmtk_free_fw_cfg_struct(bt_woble->woble_setting_apcf_resume, WOBLE_SETTING_COUNT);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_off, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_off_status_event, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_off_comp_event, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_on, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_on_status_event, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_radio_on_comp_event, 1);
+ btmtk_free_fw_cfg_struct(&bt_woble->woble_setting_wakeup_type, 1);
+
+ bt_woble->woble_setting_len = 0;
+
+ kfree(bt_woble->woble_setting_file_name);
+ bt_woble->woble_setting_file_name = NULL;
+}
+
+int btmtk_woble_initialize(struct btmtk_dev *bdev, struct btmtk_woble *bt_woble)
+{
+ int err = 0;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ bt_woble->bdev = bdev;
+ /* Need to add Woble flow */
+ if (is_support_unify_woble(bdev)) {
+ if (bt_woble->woble_setting_file_name == NULL) {
+ bt_woble->woble_setting_file_name = kzalloc(MAX_BIN_FILE_NAME_LEN, GFP_KERNEL);
+ if (!bt_woble->woble_setting_file_name) {
+ BTMTK_ERR("%s: alloc memory fail (bt_woble->woble_setting_file_name)", __func__);
+ err = -1;
+ goto end;
+ }
+ }
+
+ (void)snprintf(bt_woble->woble_setting_file_name, MAX_BIN_FILE_NAME_LEN,
+ "%s_%x.%s", WOBLE_CFG_NAME_PREFIX, bdev->chip_id & 0xffff,
+ WOBLE_CFG_NAME_SUFFIX);
+
+ BTMTK_INFO("%s: woble setting file name is %s", __func__, bt_woble->woble_setting_file_name);
+
+ btmtk_load_woble_setting(bt_woble->woble_setting_file_name,
+ bdev->intf_dev,
+ &bt_woble->woble_setting_len,
+ bt_woble);
+ /* if reset_stack is true, when chip reset is done, we need to power on chip to do
+ * reset stack
+ */
+ if (bmain_info->reset_stack_flag) {
+ err = btmtk_reset_power_on(bdev);
+ if (err < 0) {
+ BTMTK_ERR("reset power on failed!");
+ goto err;
+ }
+ }
+ }
+
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ btmtk_woble_input_init(bt_woble);
+ btmtk_RegisterBTIrq(bt_woble);
+ }
+
+ return 0;
+
+err:
+ btmtk_free_woble_setting_file(bt_woble);
+end:
+ return err;
+}
+
+void btmtk_woble_uninitialize(struct btmtk_woble *bt_woble)
+{
+ struct btmtk_dev *bdev = bt_woble->bdev;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev == NULL", __func__);
+ return;
+ }
+
+ BTMTK_INFO("%s begin", __func__);
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ if (bt_woble->wobt_irq != 0 && atomic_read(&(bt_woble->irq_enable_count)) == 1) {
+ BTMTK_INFO("disable BT IRQ:%d", bt_woble->wobt_irq);
+ atomic_dec(&(bt_woble->irq_enable_count));
+ disable_irq_nosync(bt_woble->wobt_irq);
+ } else
+ BTMTK_INFO("irq_enable count:%d", atomic_read(&(bt_woble->irq_enable_count)));
+ if (bt_woble->wobt_irq) {
+ free_irq(bt_woble->wobt_irq, bt_woble);
+ bt_woble->wobt_irq = 0;
+ }
+
+ btmtk_woble_input_deinit(bt_woble);
+ }
+
+ btmtk_free_woble_setting_file(bt_woble);
+ bt_woble->bdev = NULL;
+}
diff --git a/cfg/bt.cfg b/cfg/bt.cfg
new file mode 100644
index 0000000..edf5c07
--- /dev/null
+++ b/cfg/bt.cfg
@@ -0,0 +1,37 @@
+# bt.cfg,now just parse '#' for annotation
+SUPPORT_UNIFY_WOBLE 1
+# this item is valid only when SUPPORT_UNIFY_WOBLE enabled. 0: LEGACY, LOW or HIGH. 1: Waveform. 2: IR
+UNIFY_WOBLE_TYPE 1
+SUPPORT_LEGACY_WOBLE 0
+SUPPORT_WOBLE_BY_EINT 0
+BT_DONGLE_RESET_GPIO_PIN 220
+SAVE_FW_DUMP_IN_KERNEL 0
+# this item is valid only when support save fw_dump in kernel
+SYS_LOG_FILE_NAME /sdcard/bt_sys_log
+# this item is valid only when support save fw_dump in kernel
+FW_DUMP_FILE_NAME /sdcard/bt_fw_dump
+SUPPORT_DONGLE_RESET 1
+SUPPORT_FULL_FW_DUMP 0
+SUPPORT_WOBLE_WAKELOCK 1
+SUPPORT_WOBLE_FOR_BT_DISABLE 1
+RESET_STACK_AFTER_WOBLE 1
+SUPPORT_BT_SINGLE_SKU 1
+
+SUPPORT_BT_AUDIO_SETTING 1
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1
+AUDIO_ENABLE_CMD: 0x49, 0x00, 0x80, 0x00,
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1. Only for Kiwi, set pinmux num & mode
+AUDIO_PINMUX_NUM: 0x00, 0x01, 0x02, 0x04,
+AUDIO_PINMUX_MODE: 0x01, 0x01, 0x01, 0x01,
+
+SUPPORT_PICUS_TO_HOST 0
+SUPPORT_AUTO_PICUS 0
+PICUS_ENABLE_COMMAND: 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02,
+PICUS_FILTER_COMMAND: 0x5F, 0xFC, 0x2E, 0x50, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+WMT_CMD000: 0x6F, 0xFC, 0x06, 0x01, 0x06, 0x02, 0x00, 0x03, 0x01,
+# phase1 means this wmt cmd should be send to FW after download patch or after BT power on
+# this wmt cmd used to enable bus vio
+PHASE1_WMT_CMD000: 0x6F, 0xFC, 0x08, 0x01, 0x02, 0x04, 0x00, 0x1D, 0x01, 0x00, 0x00,
+#vendor cmd need to send to FW after BT power on
+VENDOR_CMD000: 0x96, 0xFD, 0x18, 0x03, 0x03, 0x06, 0x0E, 0x03, 0x03, 0x07, 0x0E, 0x03, 0x03, 0x08, 0x0E, 0x03, 0x03, 0x0B, 0x11, 0x03, 0x03, 0x0D, 0x0C, 0x03, 0x03, 0x13, 0x10,
diff --git a/cfg/bt_mt7961_1_1.cfg b/cfg/bt_mt7961_1_1.cfg
new file mode 100644
index 0000000..08d1d8b
--- /dev/null
+++ b/cfg/bt_mt7961_1_1.cfg
@@ -0,0 +1,30 @@
+# bt.cfg,now just parse '#' for annotation
+SUPPORT_UNIFY_WOBLE 1
+# this item is valid only when SUPPORT_UNIFY_WOBLE enabled. 0: LEGACY, LOW or HIGH. 1: Waveform. 2: IR
+UNIFY_WOBLE_TYPE 1
+SUPPORT_LEGACY_WOBLE 0
+SUPPORT_WOBLE_BY_EINT 0
+BT_DONGLE_RESET_GPIO_PIN 220
+SAVE_FW_DUMP_IN_KERNEL 0
+# this item is valid only when support save fw_dump in kernel
+SYS_LOG_FILE_NAME /sdcard/bt_sys_log
+# this item is valid only when support save fw_dump in kernel
+FW_DUMP_FILE_NAME /sdcard/bt_fw_dump
+SUPPORT_DONGLE_RESET 1
+SUPPORT_FULL_FW_DUMP 0
+SUPPORT_WOBLE_WAKELOCK 0
+SUPPORT_WOBLE_FOR_BT_DISABLE 1
+RESET_STACK_AFTER_WOBLE 1
+SUPPORT_BT_SINGLE_SKU 0
+
+SUPPORT_BT_AUDIO_SETTING 1
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1
+AUDIO_ENABLE_CMD: 0x49, 0x00, 0x80, 0x00,
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1. Only for Kiwi, set pinmux num & mode
+AUDIO_PINMUX_NUM: 0x00, 0x01, 0x02, 0x04,
+AUDIO_PINMUX_MODE: 0x01, 0x01, 0x01, 0x01,
+
+SUPPORT_PICUS_TO_HOST 0
+SUPPORT_AUTO_PICUS 0
+PICUS_ENABLE_COMMAND: 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02,
+PICUS_FILTER_COMMAND: 0x5F, 0xFC, 0x2E, 0x50, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/cfg/bt_mt7961_1_2.cfg b/cfg/bt_mt7961_1_2.cfg
new file mode 100644
index 0000000..08d1d8b
--- /dev/null
+++ b/cfg/bt_mt7961_1_2.cfg
@@ -0,0 +1,30 @@
+# bt.cfg,now just parse '#' for annotation
+SUPPORT_UNIFY_WOBLE 1
+# this item is valid only when SUPPORT_UNIFY_WOBLE enabled. 0: LEGACY, LOW or HIGH. 1: Waveform. 2: IR
+UNIFY_WOBLE_TYPE 1
+SUPPORT_LEGACY_WOBLE 0
+SUPPORT_WOBLE_BY_EINT 0
+BT_DONGLE_RESET_GPIO_PIN 220
+SAVE_FW_DUMP_IN_KERNEL 0
+# this item is valid only when support save fw_dump in kernel
+SYS_LOG_FILE_NAME /sdcard/bt_sys_log
+# this item is valid only when support save fw_dump in kernel
+FW_DUMP_FILE_NAME /sdcard/bt_fw_dump
+SUPPORT_DONGLE_RESET 1
+SUPPORT_FULL_FW_DUMP 0
+SUPPORT_WOBLE_WAKELOCK 0
+SUPPORT_WOBLE_FOR_BT_DISABLE 1
+RESET_STACK_AFTER_WOBLE 1
+SUPPORT_BT_SINGLE_SKU 0
+
+SUPPORT_BT_AUDIO_SETTING 1
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1
+AUDIO_ENABLE_CMD: 0x49, 0x00, 0x80, 0x00,
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1. Only for Kiwi, set pinmux num & mode
+AUDIO_PINMUX_NUM: 0x00, 0x01, 0x02, 0x04,
+AUDIO_PINMUX_MODE: 0x01, 0x01, 0x01, 0x01,
+
+SUPPORT_PICUS_TO_HOST 0
+SUPPORT_AUTO_PICUS 0
+PICUS_ENABLE_COMMAND: 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02,
+PICUS_FILTER_COMMAND: 0x5F, 0xFC, 0x2E, 0x50, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/cfg/bt_mt7961_1a_1.cfg b/cfg/bt_mt7961_1a_1.cfg
new file mode 100644
index 0000000..08d1d8b
--- /dev/null
+++ b/cfg/bt_mt7961_1a_1.cfg
@@ -0,0 +1,30 @@
+# bt.cfg,now just parse '#' for annotation
+SUPPORT_UNIFY_WOBLE 1
+# this item is valid only when SUPPORT_UNIFY_WOBLE enabled. 0: LEGACY, LOW or HIGH. 1: Waveform. 2: IR
+UNIFY_WOBLE_TYPE 1
+SUPPORT_LEGACY_WOBLE 0
+SUPPORT_WOBLE_BY_EINT 0
+BT_DONGLE_RESET_GPIO_PIN 220
+SAVE_FW_DUMP_IN_KERNEL 0
+# this item is valid only when support save fw_dump in kernel
+SYS_LOG_FILE_NAME /sdcard/bt_sys_log
+# this item is valid only when support save fw_dump in kernel
+FW_DUMP_FILE_NAME /sdcard/bt_fw_dump
+SUPPORT_DONGLE_RESET 1
+SUPPORT_FULL_FW_DUMP 0
+SUPPORT_WOBLE_WAKELOCK 0
+SUPPORT_WOBLE_FOR_BT_DISABLE 1
+RESET_STACK_AFTER_WOBLE 1
+SUPPORT_BT_SINGLE_SKU 0
+
+SUPPORT_BT_AUDIO_SETTING 1
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1
+AUDIO_ENABLE_CMD: 0x49, 0x00, 0x80, 0x00,
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1. Only for Kiwi, set pinmux num & mode
+AUDIO_PINMUX_NUM: 0x00, 0x01, 0x02, 0x04,
+AUDIO_PINMUX_MODE: 0x01, 0x01, 0x01, 0x01,
+
+SUPPORT_PICUS_TO_HOST 0
+SUPPORT_AUTO_PICUS 0
+PICUS_ENABLE_COMMAND: 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02,
+PICUS_FILTER_COMMAND: 0x5F, 0xFC, 0x2E, 0x50, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/cfg/bt_mt7961_1a_2.cfg b/cfg/bt_mt7961_1a_2.cfg
new file mode 100644
index 0000000..08d1d8b
--- /dev/null
+++ b/cfg/bt_mt7961_1a_2.cfg
@@ -0,0 +1,30 @@
+# bt.cfg,now just parse '#' for annotation
+SUPPORT_UNIFY_WOBLE 1
+# this item is valid only when SUPPORT_UNIFY_WOBLE enabled. 0: LEGACY, LOW or HIGH. 1: Waveform. 2: IR
+UNIFY_WOBLE_TYPE 1
+SUPPORT_LEGACY_WOBLE 0
+SUPPORT_WOBLE_BY_EINT 0
+BT_DONGLE_RESET_GPIO_PIN 220
+SAVE_FW_DUMP_IN_KERNEL 0
+# this item is valid only when support save fw_dump in kernel
+SYS_LOG_FILE_NAME /sdcard/bt_sys_log
+# this item is valid only when support save fw_dump in kernel
+FW_DUMP_FILE_NAME /sdcard/bt_fw_dump
+SUPPORT_DONGLE_RESET 1
+SUPPORT_FULL_FW_DUMP 0
+SUPPORT_WOBLE_WAKELOCK 0
+SUPPORT_WOBLE_FOR_BT_DISABLE 1
+RESET_STACK_AFTER_WOBLE 1
+SUPPORT_BT_SINGLE_SKU 0
+
+SUPPORT_BT_AUDIO_SETTING 1
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1
+AUDIO_ENABLE_CMD: 0x49, 0x00, 0x80, 0x00,
+# this item is vaild only when SUPPORT_BT_AUDIO_SETTING = 1. Only for Kiwi, set pinmux num & mode
+AUDIO_PINMUX_NUM: 0x00, 0x01, 0x02, 0x04,
+AUDIO_PINMUX_MODE: 0x01, 0x01, 0x01, 0x01,
+
+SUPPORT_PICUS_TO_HOST 0
+SUPPORT_AUTO_PICUS 0
+PICUS_ENABLE_COMMAND: 0x01, 0x5D, 0xFC, 0x04, 0x00, 0x00, 0x02, 0x02,
+PICUS_FILTER_COMMAND: 0x5F, 0xFC, 0x2E, 0x50, 0x01, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/cfg/woble_setting_7961.bin b/cfg/woble_setting_7961.bin
new file mode 100644
index 0000000..65a14b3
--- /dev/null
+++ b/cfg/woble_setting_7961.bin
@@ -0,0 +1,28 @@
+# File path depends on kernel request_firmware API, EX: /etc/firmware or /lib/firmware
+# Format:
+# 1. ":" is necessary between name & command
+# 2. HEX need prefix '0x'
+# 3. Each HEX end need ','
+# Only support 10 groups setting, add scan interval and window
+
+Turnkey:
+APCF00:0x57, 0xFD, 0x27, 0x06, 0x00, 0x1E, 0x46, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x4B, 0x54, 0x4D, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+APCF_ADD_MAC00:0x01,
+APCF_ADD_MAC_LOCATION00:0x09,
+
+APCF01:0x57, 0xFD, 0x0A, 0x01, 0x00, 0x1E, 0x20, 0x00, 0x20, 0x00, 0x01, 0x80, 0x00,
+APCF_ADD_MAC01:0x00,
+APCF_ADD_MAC_LOCATION01:0x00,
+
+RADIOOFF00:0xC9, 0xFC, 0x1E, 0x01, 0x20, 0x02, 0x00, 0x01, 0x02, 0x01, 0x00, 0x02, 0x40, 0x1E, 0x02, 0x41, 0x0F, 0x05, 0x24, 0x20, 0x04, 0x32, 0x00, 0x09, 0x26, 0xC0, 0x12, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+WAKEUP_TYPE_LEGACY00:0x05, 0x10, 0x00, 0x00, 0x6B, 0x00, 0x04, 0x42, 0x09, 0x01, 0x02, 0x0A, 0x48, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0x00, 0x80,
+WAKEUP_TYPE_WAVEFORM00:0x05, 0x10, 0x00, 0x00, 0x20, 0x00, 0x04, 0x42, 0x09, 0x01, 0x02, 0xA2, 0x48, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0x00, 0xFE, 0xA5, 0xA5, 0x00, 0x00, 0x10, 0x00, 0x4A, 0xFF, 0x5A, 0x5A, 0x00, 0x00, 0x10, 0x00, 0xB4, 0xFE, 0x96, 0x96, 0x00, 0x00, 0x10, 0x00, 0x2C, 0xFF, 0x69, 0x69, 0x00, 0x00, 0x10, 0x00, 0xD2, 0xFE, 0x78, 0x78, 0x00, 0x00, 0x10, 0x00, 0xF0, 0xFE, 0x11, 0x11, 0x00, 0x00, 0x10, 0x00, 0x22, 0xFE, 0x22, 0x22, 0x00, 0x00, 0x10, 0x00, 0x44, 0xFE, 0x33, 0x33, 0x00, 0x00, 0x10, 0x00, 0x66, 0xFE, 0x44, 0x44, 0x00, 0x00, 0x10, 0x00, 0x88, 0xFE, 0x55, 0x55, 0x00, 0x00, 0x10, 0x00, 0xAA, 0xFE, 0x66, 0x66, 0x00, 0x00, 0x10, 0x00, 0xCC, 0xFE, 0x77, 0x77, 0x00, 0x00, 0x10, 0x00, 0xEE, 0xFE, 0x88, 0x88, 0x00, 0x00, 0x10, 0x00, 0x10, 0xFF, 0x99, 0x99, 0x00, 0x00, 0x10, 0x00, 0x32, 0xFF, 0xAA, 0xAA, 0x00, 0x00, 0x10, 0x00, 0x54, 0xFF, 0xBB, 0xBB, 0x00, 0x00, 0x10, 0x00, 0x76, 0xFF, 0xCC, 0xCC, 0x00, 0x00, 0x10, 0x00, 0x98, 0xFF, 0xDD, 0xDD, 0x00, 0x00, 0x10, 0x00, 0xBA, 0xFF, 0xEE, 0xEE, 0x00, 0x00, 0x10, 0x00, 0xDC, 0xFF,
+WAKEUP_TYPE_IR00:
+RADIOOFF_STATUS_EVENT00:0x0f, 0x04, 0x00, 0x01, 0xC9, 0xFC,
+RADIOOFF_COMPLETE_EVENT00:0xe6, 0x02, 0x08, 0x00,
+
+RADIOON00:0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00,
+RADIOON_STATUS_EVENT00:0x0F, 0x04, 0x00, 0x01, 0xC9, 0xFC,
+RADIOON_COMPLETE_EVENT00:0xe6, 0x02, 0x08, 0x01,
+
+APCF_RESUME00:0x57, 0xFD, 0x03, 0x01, 0x01, 0x1E,
diff --git a/debug_sop_file/sdio_debug_7902.bin b/debug_sop_file/sdio_debug_7902.bin
new file mode 100644
index 0000000..1dee971
--- /dev/null
+++ b/debug_sop_file/sdio_debug_7902.bin
@@ -0,0 +1,322 @@
+# Format:
+# 1. ":" is necessary between name & command
+# 2. HEX need prefix '0x'
+# 3. Each HEX end need ','
+# 4. command len means
+# a.len is 1 :read reg address
+# b.len is 2 :write reg address and value
+# c.len is 3 :write reg address value and read
+
+# 1 bpf0 pc log dump
+DEBUG_REG_000:0x00000034,0x00000001,
+DEBUG_REG_001:0x00000030,0x00000001,0x0000002C,
+DEBUG_REG_002:0x00000030,0x00000005,0x0000002C,
+DEBUG_REG_003:0x00000030,0x00000009,0x0000002C,
+DEBUG_REG_004:0x00000030,0x0000000D,0x0000002C,
+DEBUG_REG_005:0x00000030,0x00000011,0x0000002C,
+DEBUG_REG_006:0x00000030,0x00000015,0x0000002C,
+DEBUG_REG_007:0x00000030,0x00000019,0x0000002C,
+DEBUG_REG_008:0x00000030,0x0000001D,0x0000002C,
+DEBUG_REG_009:0x00000030,0x00000021,0x0000002C,
+DEBUG_REG_010:0x00000030,0x00000025,0x0000002C,
+DEBUG_REG_011:0x00000030,0x00000029,0x0000002C,
+DEBUG_REG_012:0x00000030,0x0000002D,0x0000002C,
+DEBUG_REG_013:0x00000030,0x00000031,0x0000002C,
+DEBUG_REG_014:0x00000030,0x00000035,0x0000002C,
+DEBUG_REG_015:0x00000030,0x00000039,0x0000002C,
+DEBUG_REG_016:0x00000030,0x0000003D,0x0000002C,
+DEBUG_REG_017:0x00000030,0x00000041,0x0000002C,
+DEBUG_REG_018:0x00000030,0x00000045,0x0000002C,
+DEBUG_REG_019:0x00000030,0x00000049,0x0000002C,
+DEBUG_REG_020:0x00000030,0x0000004D,0x0000002C,
+DEBUG_REG_021:0x00000030,0x00000051,0x0000002C,
+DEBUG_REG_022:0x00000030,0x00000055,0x0000002C,
+DEBUG_REG_023:0x00000030,0x00000059,0x0000002C,
+DEBUG_REG_024:0x00000030,0x0000005D,0x0000002C,
+DEBUG_REG_025:0x00000030,0x00000061,0x0000002C,
+DEBUG_REG_026:0x00000030,0x00000065,0x0000002C,
+DEBUG_REG_027:0x00000030,0x00000069,0x0000002C,
+DEBUG_REG_028:0x00000030,0x0000006D,0x0000002C,
+DEBUG_REG_029:0x00000030,0x00000071,0x0000002C,
+DEBUG_REG_030:0x00000030,0x00000075,0x0000002C,
+DEBUG_REG_031:0x00000030,0x00000079,0x0000002C,
+DEBUG_REG_032:0x00000030,0x0000007D,0x0000002C,
+DEBUG_REG_033:0x00000030,0x00000081,0x0000002C,
+DEBUG_REG_034:0x00000030,0x00000085,0x0000002C,
+DEBUG_REG_035:0x00000030,0x00000089,0x0000002C,
+DEBUG_REG_036:0x00000030,0x0000008D,0x0000002C,
+DEBUG_REG_037:0x00000030,0x00000091,0x0000002C,
+DEBUG_REG_038:0x00000030,0x00000095,0x0000002C,
+DEBUG_REG_039:0x00000030,0x00000099,0x0000002C,
+DEBUG_REG_040:0x00000030,0x0000009D,0x0000002C,
+DEBUG_REG_041:0x00000030,0x000000A1,0x0000002C,
+DEBUG_REG_042:0x00000030,0x000000A5,0x0000002C,
+DEBUG_REG_043:0x00000030,0x000000A9,0x0000002C,
+DEBUG_REG_044:0x00000030,0x000000AD,0x0000002C,
+DEBUG_REG_045:0x00000030,0x000000B1,0x0000002C,
+
+# 2 bpf0 GPR dump
+DEBUG_REG_046:0x00000034,0x00000001,
+DEBUG_REG_047:0x00000030,0x010000AD,0x0000002C,
+DEBUG_REG_048:0x00000030,0x020000AD,0x0000002C,
+DEBUG_REG_049:0x00000030,0x030000AD,0x0000002C,
+DEBUG_REG_050:0x00000030,0x040000AD,0x0000002C,
+DEBUG_REG_051:0x00000030,0x050000AD,0x0000002C,
+DEBUG_REG_052:0x00000030,0x060000AD,0x0000002C,
+DEBUG_REG_053:0x00000030,0x070000AD,0x0000002C,
+DEBUG_REG_054:0x00000030,0x080000AD,0x0000002C,
+DEBUG_REG_055:0x00000030,0x090000AD,0x0000002C,
+DEBUG_REG_056:0x00000030,0x0a0000AD,0x0000002C,
+DEBUG_REG_057:0x00000030,0x0b0000AD,0x0000002C,
+DEBUG_REG_058:0x00000030,0x0c0000AD,0x0000002C,
+DEBUG_REG_059:0x00000030,0x0d0000AD,0x0000002C,
+DEBUG_REG_060:0x00000030,0x0e0000AD,0x0000002C,
+DEBUG_REG_061:0x00000030,0x0f0000AD,0x0000002C,
+DEBUG_REG_062:0x00000030,0x100000AD,0x0000002C,
+DEBUG_REG_063:0x00000030,0x110000AD,0x0000002C,
+DEBUG_REG_064:0x00000030,0x120000AD,0x0000002C,
+DEBUG_REG_065:0x00000030,0x130000AD,0x0000002C,
+DEBUG_REG_066:0x00000030,0x140000AD,0x0000002C,
+DEBUG_REG_067:0x00000030,0x150000AD,0x0000002C,
+DEBUG_REG_068:0x00000030,0x160000AD,0x0000002C,
+DEBUG_REG_069:0x00000030,0x170000AD,0x0000002C,
+DEBUG_REG_070:0x00000030,0x180000AD,0x0000002C,
+DEBUG_REG_071:0x00000030,0x190000AD,0x0000002C,
+DEBUG_REG_072:0x00000030,0x1a0000AD,0x0000002C,
+DEBUG_REG_073:0x00000030,0x1b0000AD,0x0000002C,
+DEBUG_REG_074:0x00000030,0x1c0000AD,0x0000002C,
+DEBUG_REG_075:0x00000030,0x1d0000AD,0x0000002C,
+DEBUG_REG_076:0x00000030,0x1e0000AD,0x0000002C,
+DEBUG_REG_077:0x00000030,0x1f0000AD,0x0000002C,
+DEBUG_REG_078:0x00000030,0x200000AD,0x0000002C,
+DEBUG_REG_079:0x00000030,0x210000AD,0x0000002C,
+DEBUG_REG_080:0x00000030,0x220000AD,0x0000002C,
+DEBUG_REG_081:0x00000030,0x230000AD,0x0000002C,
+DEBUG_REG_082:0x00000030,0x240000AD,0x0000002C,
+DEBUG_REG_083:0x00000030,0x250000AD,0x0000002C,
+
+# 3 bpf0_mcusys debug flag dump
+DEBUG_REG_084:0x00000034,0x00000001,
+DEBUG_REG_085:0x00000030,0x000200A9,0x0000002C,
+DEBUG_REG_086:0x00000030,0x000402A9,0x0000002C,
+DEBUG_REG_087:0x00000030,0x000604A9,0x0000002C,
+DEBUG_REG_088:0x00000030,0x000806A9,0x0000002C,
+DEBUG_REG_089:0x00000030,0x000A08A9,0x0000002C,
+DEBUG_REG_090:0x00000030,0x000C0AA9,0x0000002C,
+DEBUG_REG_091:0x00000030,0x000E0CA9,0x0000002C,
+DEBUG_REG_092:0x00000030,0x00100EA9,0x0000002C,
+DEBUG_REG_093:0x00000030,0x001210A9,0x0000002C,
+DEBUG_REG_094:0x00000030,0x001412A9,0x0000002C,
+DEBUG_REG_095:0x00000030,0x001614A9,0x0000002C,
+DEBUG_REG_096:0x00000030,0x001816A9,0x0000002C,
+DEBUG_REG_097:0x00000030,0x001A18A9,0x0000002C,
+DEBUG_REG_098:0x00000030,0x001C1AA9,0x0000002C,
+DEBUG_REG_099:0x00000030,0x001E1CA9,0x0000002C,
+DEBUG_REG_100:0x00000030,0x00201EA9,0x0000002C,
+DEBUG_REG_101:0x00000030,0x002220A9,0x0000002C,
+DEBUG_REG_102:0x00000030,0x002422A9,0x0000002C,
+DEBUG_REG_103:0x00000030,0x002624A9,0x0000002C,
+DEBUG_REG_104:0x00000030,0x002826A9,0x0000002C,
+DEBUG_REG_105:0x00000030,0x002A28A9,0x0000002C,
+DEBUG_REG_106:0x00000030,0x002C2AA9,0x0000002C,
+DEBUG_REG_107:0x00000030,0x002E2CA9,0x0000002C,
+DEBUG_REG_108:0x00000030,0x00302EA9,0x0000002C,
+DEBUG_REG_109:0x00000030,0x003230A9,0x0000002C,
+DEBUG_REG_110:0x00000030,0x003432A9,0x0000002C,
+DEBUG_REG_111:0x00000030,0x003634A9,0x0000002C,
+DEBUG_REG_112:0x00000030,0x003836A9,0x0000002C,
+DEBUG_REG_113:0x00000030,0x003A38A9,0x0000002C,
+DEBUG_REG_114:0x00000030,0x003C3AA9,0x0000002C,
+DEBUG_REG_115:0x00000030,0x003E3CA9,0x0000002C,
+DEBUG_REG_116:0x00000030,0x00403EA9,0x0000002C,
+DEBUG_REG_117:0x00000030,0x004240A9,0x0000002C,
+DEBUG_REG_118:0x00000030,0x004442A9,0x0000002C,
+DEBUG_REG_119:0x00000044,0x004644A9,0x0000002C,
+DEBUG_REG_120:0x00000030,0x004846A9,0x0000002C,
+DEBUG_REG_121:0x00000030,0x004A48A9,0x0000002C,
+DEBUG_REG_122:0x00000030,0x004C4AA9,0x0000002C,
+DEBUG_REG_123:0x00000030,0x004E4CA9,0x0000002C,
+DEBUG_REG_124:0x00000030,0x00504EA9,0x0000002C,
+DEBUG_REG_125:0x00000030,0x005250A9,0x0000002C,
+DEBUG_REG_126:0x00000030,0x005452A9,0x0000002C,
+DEBUG_REG_127:0x00000030,0x005654A9,0x0000002C,
+DEBUG_REG_128:0x00000030,0x005856A9,0x0000002C,
+DEBUG_REG_129:0x00000030,0x005A58A9,0x0000002C,
+DEBUG_REG_130:0x00000030,0x005C5AA9,0x0000002C,
+DEBUG_REG_131:0x00000030,0x005E5CA9,0x0000002C,
+DEBUG_REG_132:0x00000030,0x00605EA9,0x0000002C,
+DEBUG_REG_133:0x00000030,0x006260A9,0x0000002C,
+DEBUG_REG_134:0x00000030,0x006462A9,0x0000002C,
+DEBUG_REG_135:0x00000030,0x006664A9,0x0000002C,
+DEBUG_REG_136:0x00000030,0x006866A9,0x0000002C,
+DEBUG_REG_137:0x00000030,0x006A68A9,0x0000002C,
+DEBUG_REG_138:0x00000030,0x006C6AA9,0x0000002C,
+DEBUG_REG_139:0x00000030,0x006E6CA9,0x0000002C,
+DEBUG_REG_140:0x00000030,0x00706EA9,0x0000002C,
+DEBUG_REG_141:0x00000030,0x007270A9,0x0000002C,
+DEBUG_REG_142:0x00000030,0x007472A9,0x0000002C,
+DEBUG_REG_143:0x00000030,0x007674A9,0x0000002C,
+DEBUG_REG_144:0x00000030,0x007876A9,0x0000002C,
+DEBUG_REG_145:0x00000030,0x007A78A9,0x0000002C,
+DEBUG_REG_146:0x00000030,0x007C7AA9,0x0000002C,
+DEBUG_REG_147:0x00000030,0x007E7CA9,0x0000002C,
+DEBUG_REG_148:0x00000030,0x00807EA9,0x0000002C,
+DEBUG_REG_149:0x00000030,0x008280A9,0x0000002C,
+DEBUG_REG_150:0x00000030,0x008482A9,0x0000002C,
+DEBUG_REG_151:0x00000030,0x008684A9,0x0000002C,
+DEBUG_REG_152:0x00000030,0x008886A9,0x0000002C,
+DEBUG_REG_153:0x00000030,0x008A88A9,0x0000002C,
+DEBUG_REG_154:0x00000030,0x008C8AA9,0x0000002C,
+DEBUG_REG_155:0x00000030,0x008E8CA9,0x0000002C,
+DEBUG_REG_156:0x00000030,0x00908EA9,0x0000002C,
+DEBUG_REG_157:0x00000030,0x009290A9,0x0000002C,
+DEBUG_REG_158:0x00000044,0x009492A9,0x0000002C,
+DEBUG_REG_159:0x00000030,0x009694A9,0x0000002C,
+DEBUG_REG_160:0x00000030,0x009896A9,0x0000002C,
+DEBUG_REG_161:0x00000030,0x009A98A9,0x0000002C,
+DEBUG_REG_162:0x00000030,0x009C9AA9,0x0000002C,
+DEBUG_REG_163:0x00000030,0x009E9CA9,0x0000002C,
+DEBUG_REG_164:0x00000030,0x00A09EA9,0x0000002C,
+DEBUG_REG_165:0x00000030,0x00A2A0A9,0x0000002C,
+DEBUG_REG_166:0x00000030,0x00A4A2A9,0x0000002C,
+DEBUG_REG_167:0x00000030,0x00A6A4A9,0x0000002C,
+DEBUG_REG_168:0x00000030,0x00A8A6A9,0x0000002C,
+DEBUG_REG_169:0x00000030,0x00AAA8A9,0x0000002C,
+DEBUG_REG_170:0x00000030,0x00ACAAA9,0x0000002C,
+DEBUG_REG_171:0x00000030,0x00AEACA9,0x0000002C,
+DEBUG_REG_172:0x00000030,0x00B0AEA9,0x0000002C,
+DEBUG_REG_173:0x00000030,0x00B2B0A9,0x0000002C,
+DEBUG_REG_174:0x00000030,0x00B4B2A9,0x0000002C,
+
+#4 bbpf0 vdesigner bus hang dump
+DEBUG_REG_175:0x00000034,0x00000001,
+DEBUG_REG_176:0x00000030,0x104A48A9,0x0000002C,
+DEBUG_REG_177:0x00000030,0x104C4AA9,0x0000002C,
+DEBUG_REG_178:0x00000030,0x204A48A9,0x0000002C,
+DEBUG_REG_179:0x00000030,0x204C4AA9,0x0000002C,
+DEBUG_REG_180:0x00000030,0x304A48A9,0x0000002C,
+DEBUG_REG_181:0x00000030,0x304C4AA9,0x0000002C,
+DEBUG_REG_182:0x00000030,0x404A48A9,0x0000002C,
+DEBUG_REG_183:0x00000030,0x404C4AA9,0x0000002C,
+DEBUG_REG_184:0x00000030,0x504A48A9,0x0000002C,
+DEBUG_REG_185:0x00000030,0x504C4AA9,0x0000002C,
+DEBUG_REG_186:0x00000030,0x604A48A9,0x0000002C,
+DEBUG_REG_187:0x00000030,0x604C4AA9,0x0000002C,
+DEBUG_REG_188:0x00000030,0x704A48A9,0x0000002C,
+DEBUG_REG_189:0x00000030,0x704C4AA9,0x0000002C,
+DEBUG_REG_190:0x00000030,0x804A48A9,0x0000002C,
+DEBUG_REG_191:0x00000030,0x804C4AA9,0x0000002C,
+DEBUG_REG_192:0x00000030,0x904A48A9,0x0000002C,
+DEBUG_REG_193:0x00000030,0x904C4AA9,0x0000002C,
+DEBUG_REG_194:0x00000030,0xA04A48A9,0x0000002C,
+DEBUG_REG_195:0x00000030,0xA04C4AA9,0x0000002C,
+
+# 5 BGF top
+DEBUG_REG_196:0x00000034,0x00000003,
+DEBUG_REG_197:0x00000030,0x91800000,0x0000002C,
+DEBUG_REG_198:0x00000030,0x90880000,0x0000002C,
+
+DEBUG_REG_199:0x00000034,0x00000000,
+DEBUG_REG_200:0x00000030,0x80000180,0x0000002C,
+DEBUG_REG_201:0x00000030,0x80000181,0x0000002C,
+DEBUG_REG_202:0x00000030,0x80000182,0x0000002C,
+DEBUG_REG_203:0x00000030,0x80000183,0x0000002C,
+DEBUG_REG_204:0x00000030,0x80000184,0x0000002C,
+DEBUG_REG_205:0x00000030,0x80000185,0x0000002C,
+DEBUG_REG_206:0x00000030,0x80000186,0x0000002C,
+DEBUG_REG_207:0x00000030,0x80000187,0x0000002C,
+DEBUG_REG_208:0x00000030,0x80000188,0x0000002C,
+DEBUG_REG_209:0x00000030,0x80000189,0x0000002C,
+DEBUG_REG_210:0x00000030,0x8000018a,0x0000002C,
+DEBUG_REG_211:0x00000030,0x8000018b,0x0000002C,
+DEBUG_REG_212:0x00000030,0x8000018c,0x0000002C,
+DEBUG_REG_213:0x00000030,0x8000018d,0x0000002C,
+DEBUG_REG_214:0x00000030,0x8000018e,0x0000002C,
+DEBUG_REG_215:0x00000030,0x8000018f,0x0000002C,
+DEBUG_REG_216:0x00000030,0x80000190,0x0000002C,
+DEBUG_REG_217:0x00000030,0x80000191,0x0000002C,
+DEBUG_REG_218:0x00000030,0x80000192,0x0000002C,
+DEBUG_REG_219:0x00000030,0x80000193,0x0000002C,
+DEBUG_REG_220:0x00000030,0x80000194,0x0000002C,
+DEBUG_REG_221:0x00000030,0x80000195,0x0000002C,
+DEBUG_REG_222:0x00000030,0x80000196,0x0000002C,
+DEBUG_REG_223:0x00000030,0x80000197,0x0000002C,
+DEBUG_REG_224:0x00000030,0x80000198,0x0000002C,
+DEBUG_REG_225:0x00000030,0x80000199,0x0000002C,
+DEBUG_REG_226:0x00000030,0x8000019a,0x0000002C,
+DEBUG_REG_227:0x00000030,0x8000019b,0x0000002C,
+DEBUG_REG_228:0x00000030,0x8000019c,0x0000002C,
+DEBUG_REG_229:0x00000030,0x8000019d,0x0000002C,
+DEBUG_REG_230:0x00000030,0x8000019e,0x0000002C,
+DEBUG_REG_231:0x00000030,0x8000019f,0x0000002C,
+DEBUG_REG_232:0x00000030,0x800001a0,0x0000002C,
+DEBUG_REG_233:0x00000030,0x800001a1,0x0000002C,
+DEBUG_REG_234:0x00000030,0x800001a2,0x0000002C,
+DEBUG_REG_235:0x00000030,0x800001a3,0x0000002C,
+DEBUG_REG_236:0x00000030,0x800001a4,0x0000002C,
+DEBUG_REG_237:0x00000030,0x800001a5,0x0000002C,
+DEBUG_REG_238:0x00000030,0x800001a6,0x0000002C,
+DEBUG_REG_239:0x00000030,0x800001a7,0x0000002C,
+DEBUG_REG_240:0x00000030,0x800001a8,0x0000002C,
+DEBUG_REG_241:0x00000030,0x800001a9,0x0000002C,
+DEBUG_REG_242:0x00000030,0x800001aa,0x0000002C,
+DEBUG_REG_243:0x00000030,0x800001ab,0x0000002C,
+DEBUG_REG_244:0x00000030,0x800001ac,0x0000002C,
+DEBUG_REG_245:0x00000030,0x800001ad,0x0000002C,
+DEBUG_REG_246:0x00000030,0x800001ae,0x0000002C,
+DEBUG_REG_247:0x00000030,0x800001af,0x0000002C,
+DEBUG_REG_248:0x00000030,0x800001b0,0x0000002C,
+DEBUG_REG_249:0x00000030,0x800001b1,0x0000002C,
+DEBUG_REG_250:0x00000030,0x800001b2,0x0000002C,
+DEBUG_REG_251:0x00000030,0x800001b3,0x0000002C,
+DEBUG_REG_252:0x00000030,0x800001b4,0x0000002C,
+DEBUG_REG_253:0x00000030,0x800001b5,0x0000002C,
+DEBUG_REG_254:0x00000030,0x800001b6,0x0000002C,
+DEBUG_REG_255:0x00000030,0x800001b7,0x0000002C,
+DEBUG_REG_256:0x00000030,0x800001b8,0x0000002C,
+DEBUG_REG_257:0x00000030,0x800001b9,0x0000002C,
+DEBUG_REG_258:0x00000030,0x800001ba,0x0000002C,
+DEBUG_REG_259:0x00000030,0x800001bb,0x0000002C,
+DEBUG_REG_260:0x00000030,0x800001bc,0x0000002C,
+DEBUG_REG_261:0x00000030,0x800001bd,0x0000002C,
+DEBUG_REG_262:0x00000030,0x800001be,0x0000002C,
+DEBUG_REG_263:0x00000030,0x8000019f,0x0000002C,
+DEBUG_REG_264:0x00000030,0x800001f0,0x0000002C,
+DEBUG_REG_265:0x00000030,0x800001f1,0x0000002C,
+DEBUG_REG_266:0x00000030,0x800001f2,0x0000002C,
+DEBUG_REG_267:0x00000030,0x800001f3,0x0000002C,
+
+# 6 BUS hang
+DEBUG_REG_268:0x00000034,0x00000001,
+DEBUG_REG_269:0x00000030,0x007675A9,0x0000002C,
+DEBUG_REG_270:0x00000030,0x007877A9,0x0000002C,
+DEBUG_REG_271:0x00000030,0x007A79A9,0x0000002C,
+DEBUG_REG_272:0x00000030,0x007C7BA9,0x0000002C,
+DEBUG_REG_273:0x00000030,0x007E7DA9,0x0000002C,
+DEBUG_REG_274:0x00000030,0x00807FA9,0x0000002C,
+DEBUG_REG_275:0x00000030,0x008281A9,0x0000002C,
+DEBUG_REG_276:0x00000030,0x008483A9,0x0000002C,
+DEBUG_REG_277:0x00000030,0x008685A9,0x0000002C,
+DEBUG_REG_278:0x00000030,0x008887A9,0x0000002C,
+DEBUG_REG_279:0x00000030,0x008A89A9,0x0000002C,
+DEBUG_REG_280:0x00000030,0x008C8BA9,0x0000002C,
+DEBUG_REG_281:0x00000030,0x008E8DA9,0x0000002C,
+DEBUG_REG_282:0x00000030,0x00908FA9,0x0000002C,
+DEBUG_REG_283:0x00000030,0x009291A9,0x0000002C,
+DEBUG_REG_284:0x00000030,0x009493A9,0x0000002C,
+DEBUG_REG_285:0x00000030,0x009695A9,0x0000002C,
+DEBUG_REG_286:0x00000030,0x009897A9,0x0000002C,
+DEBUG_REG_287:0x00000030,0x009A99A9,0x0000002C,
+DEBUG_REG_288:0x00000030,0x009C9BA9,0x0000002C,
+DEBUG_REG_289:0x00000030,0x009E9DA9,0x0000002C,
+DEBUG_REG_290:0x00000030,0x00A09FA9,0x0000002C,
+DEBUG_REG_291:0x00000030,0x00A2A1A9,0x0000002C,
+DEBUG_REG_292:0x00000030,0x00A4A3A9,0x0000002C,
+DEBUG_REG_293:0x00000030,0x00A6A5A9,0x0000002C,
+DEBUG_REG_294:0x00000030,0x00A8A7A9,0x0000002C,
+DEBUG_REG_295:0x00000030,0x00AAA9A9,0x0000002C,
+DEBUG_REG_296:0x00000030,0x00ACABA9,0x0000002C,
+DEBUG_REG_297:0x00000030,0x00AEADA9,0x0000002C,
+DEBUG_REG_298:0x00000030,0x00B0AFA9,0x0000002C,
+
+DEBUG_REG_NUM:299,
\ No newline at end of file
diff --git a/debug_sop_file/sdio_debug_7961.bin b/debug_sop_file/sdio_debug_7961.bin
new file mode 100644
index 0000000..4279335
--- /dev/null
+++ b/debug_sop_file/sdio_debug_7961.bin
@@ -0,0 +1,271 @@
+# File path depends on kernel request_firmware API, EX:etcfirmware orlibfirmware
+# Format:
+# 1. ":" is necessary between name & command
+# 2. HEX need prefix '0x'
+# 3. Each HEX end need ','
+# 4. command len means
+# a.len is 1 :read reg address
+# b.len is 2 :write reg address and value
+# c.len is 3 :write reg address value and read
+
+# power status cr
+DEBUG_REG_000:0x00000034,0x00000004,
+DEBUG_REG_001:0x00000030,0x9F1E0000,0x0000002C,
+
+# bgfsys sleep status
+DEBUG_REG_002:0x00000044,0x00000001,
+DEBUG_REG_003:0x00000030,0x80000080,0x0000002C,
+
+# mcu sleep wakeup debug
+DEBUG_REG_004:0x00000044,0x00000002,
+DEBUG_REG_005:0x00000030,0x0000FFFC,0x0000002C,
+
+# wfsys bus debug
+DEBUG_REG_006:0x00000044,0x00000001,
+DEBUG_REG_007:0x00000040,0x80000000,0x00000034,
+DEBUG_REG_008:0x00000040,0x80008421,0x00000034,
+DEBUG_REG_009:0x00000040,0x80010842,0x00000034,
+DEBUG_REG_010:0x00000040,0x80021084,0x00000034,
+DEBUG_REG_011:0x00000040,0x80084210,0x00000034,
+DEBUG_REG_012:0x00000040,0x80094A52,0x00000034,
+DEBUG_REG_013:0x00000040,0x800BDEF7,0x00000034,
+DEBUG_REG_014:0x00000040,0x800C6318,0x00000034,
+DEBUG_REG_015:0x00000040,0x800E739C,0x00000034,
+DEBUG_REG_016:0x00000040,0x800EF7BD,0x00000034,
+
+# bgfsys top debug cr
+DEBUG_REG_017:0x00000044,0x00000001,
+DEBUG_REG_018:0x00000030,0x80000000,0x0000002C,
+DEBUG_REG_019:0x00000030,0x91800000,0x0000002C,
+DEBUG_REG_020:0x00000030,0x90880000,0x0000002C,
+DEBUG_REG_021:0x00000030,0x86280080,0x0000002C,
+DEBUG_REG_022:0x00000030,0x86280081,0x0000002C,
+DEBUG_REG_023:0x00000030,0x86280082,0x0000002C,
+DEBUG_REG_024:0x00000030,0x86280083,0x0000002C,
+DEBUG_REG_025:0x00000030,0x86280084,0x0000002C,
+DEBUG_REG_026:0x00000030,0x86280085,0x0000002C,
+DEBUG_REG_027:0x00000030,0x86280086,0x0000002C,
+DEBUG_REG_028:0x00000030,0x86280087,0x0000002C,
+DEBUG_REG_029:0x00000030,0x86280088,0x0000002C,
+DEBUG_REG_030:0x00000030,0x86280089,0x0000002C,
+DEBUG_REG_031:0x00000030,0x8A480080,0x0000002C,
+DEBUG_REG_032:0x00000030,0x8A480081,0x0000002C,
+DEBUG_REG_033:0x00000030,0x8A480082,0x0000002C,
+DEBUG_REG_034:0x00000030,0x8A480083,0x0000002C,
+DEBUG_REG_035:0x00000030,0x8A480084,0x0000002C,
+DEBUG_REG_036:0x00000030,0x8A480085,0x0000002C,
+DEBUG_REG_037:0x00000030,0x8A480086,0x0000002C,
+DEBUG_REG_038:0x00000030,0x8A480087,0x0000002C,
+DEBUG_REG_039:0x00000030,0x8A480088,0x0000002C,
+DEBUG_REG_040:0x00000030,0x8A480089,0x0000002C,
+DEBUG_REG_041:0x00000030,0x80000080,0x0000002C,
+DEBUG_REG_042:0x00000030,0x80000081,0x0000002C,
+DEBUG_REG_043:0x00000030,0x80000082,0x0000002C,
+DEBUG_REG_044:0x00000030,0x80000083,0x0000002C,
+DEBUG_REG_045:0x00000030,0x80000084,0x0000002C,
+DEBUG_REG_046:0x00000030,0x80000085,0x0000002C,
+DEBUG_REG_047:0x00000030,0x80000086,0x0000002C,
+DEBUG_REG_048:0x00000030,0x80000087,0x0000002C,
+DEBUG_REG_049:0x00000030,0x80000088,0x0000002C,
+DEBUG_REG_050:0x00000030,0x80000089,0x0000002C,
+DEBUG_REG_051:0x00000030,0x8000008A,0x0000002C,
+DEBUG_REG_052:0x00000030,0x8000008B,0x0000002C,
+DEBUG_REG_053:0x00000030,0x8000008C,0x0000002C,
+DEBUG_REG_054:0x00000030,0x8000008D,0x0000002C,
+DEBUG_REG_055:0x00000030,0x8000008E,0x0000002C,
+DEBUG_REG_056:0x00000030,0x8000008F,0x0000002C,
+DEBUG_REG_057:0x00000030,0x81000080,0x0000002C,
+DEBUG_REG_058:0x00000030,0x81000081,0x0000002C,
+DEBUG_REG_059:0x00000030,0x81000082,0x0000002C,
+DEBUG_REG_060:0x00000030,0x81000083,0x0000002C,
+DEBUG_REG_061:0x00000030,0x81000084,0x0000002C,
+DEBUG_REG_062:0x00000030,0x81000085,0x0000002C,
+DEBUG_REG_063:0x00000030,0x81000086,0x0000002C,
+DEBUG_REG_064:0x00000030,0x81000087,0x0000002C,
+DEBUG_REG_065:0x00000030,0x81000088,0x0000002C,
+DEBUG_REG_066:0x00000030,0x81000089,0x0000002C,
+DEBUG_REG_067:0x00000030,0x8100008A,0x0000002C,
+DEBUG_REG_068:0x00000030,0x8100008B,0x0000002C,
+DEBUG_REG_069:0x00000030,0x8100008C,0x0000002C,
+DEBUG_REG_070:0x00000030,0x8100008D,0x0000002C,
+DEBUG_REG_071:0x00000030,0x8100008E,0x0000002C,
+DEBUG_REG_072:0x00000030,0x8100008F,0x0000002C,
+
+# 1 bpf0 pc log dump
+DEBUG_REG_073:0x00000044,0x00000000,
+DEBUG_REG_074:0x00000030,0x00000001,0x0000002C,
+DEBUG_REG_075:0x00000030,0x00000005,0x0000002C,
+DEBUG_REG_076:0x00000030,0x00000009,0x0000002C,
+DEBUG_REG_077:0x00000030,0x0000000D,0x0000002C,
+DEBUG_REG_078:0x00000030,0x00000011,0x0000002C,
+DEBUG_REG_079:0x00000030,0x00000015,0x0000002C,
+DEBUG_REG_080:0x00000030,0x00000019,0x0000002C,
+DEBUG_REG_081:0x00000030,0x0000001D,0x0000002C,
+DEBUG_REG_082:0x00000030,0x00000021,0x0000002C,
+DEBUG_REG_083:0x00000030,0x00000025,0x0000002C,
+DEBUG_REG_084:0x00000030,0x00000029,0x0000002C,
+DEBUG_REG_085:0x00000030,0x0000002D,0x0000002C,
+DEBUG_REG_086:0x00000030,0x00000031,0x0000002C,
+DEBUG_REG_087:0x00000030,0x00000035,0x0000002C,
+DEBUG_REG_088:0x00000030,0x00000039,0x0000002C,
+DEBUG_REG_089:0x00000030,0x0000003D,0x0000002C,
+DEBUG_REG_090:0x00000030,0x00000041,0x0000002C,
+DEBUG_REG_091:0x00000030,0x00000045,0x0000002C,
+DEBUG_REG_092:0x00000030,0x00000049,0x0000002C,
+DEBUG_REG_093:0x00000030,0x0000004D,0x0000002C,
+DEBUG_REG_094:0x00000030,0x00000051,0x0000002C,
+DEBUG_REG_095:0x00000030,0x00000055,0x0000002C,
+DEBUG_REG_096:0x00000030,0x00000059,0x0000002C,
+DEBUG_REG_097:0x00000030,0x0000005D,0x0000002C,
+DEBUG_REG_098:0x00000030,0x00000061,0x0000002C,
+DEBUG_REG_099:0x00000030,0x00000065,0x0000002C,
+DEBUG_REG_100:0x00000030,0x00000069,0x0000002C,
+DEBUG_REG_101:0x00000030,0x0000006D,0x0000002C,
+DEBUG_REG_102:0x00000030,0x00000071,0x0000002C,
+DEBUG_REG_103:0x00000030,0x00000075,0x0000002C,
+DEBUG_REG_104:0x00000030,0x00000079,0x0000002C,
+DEBUG_REG_105:0x00000030,0x0000007D,0x0000002C,
+DEBUG_REG_106:0x00000030,0x00000081,0x0000002C,
+DEBUG_REG_107:0x00000030,0x00000085,0x0000002C,
+DEBUG_REG_108:0x00000030,0x00000089,0x0000002C,
+DEBUG_REG_109:0x00000030,0x0000008D,0x0000002C,
+DEBUG_REG_110:0x00000030,0x00000091,0x0000002C,
+DEBUG_REG_111:0x00000030,0x00000095,0x0000002C,
+DEBUG_REG_112:0x00000030,0x00000099,0x0000002C,
+DEBUG_REG_113:0x00000030,0x0000009D,0x0000002C,
+DEBUG_REG_114:0x00000030,0x000000A1,0x0000002C,
+DEBUG_REG_115:0x00000030,0x000000A5,0x0000002C,
+DEBUG_REG_116:0x00000030,0x000000A9,0x0000002C,
+DEBUG_REG_117:0x00000030,0x000000AD,0x0000002C,
+DEBUG_REG_118:0x00000030,0x000000B1,0x0000002C,
+
+# 2 bpf0 GPR dump
+DEBUG_REG_119:0x00000044,0x00000000,
+DEBUG_REG_120:0x00000030,0x000000AD,0x0000002C,
+DEBUG_REG_121:0x00000030,0x010000AD,0x0000002C,
+DEBUG_REG_122:0x00000030,0x020000AD,0x0000002C,
+DEBUG_REG_123:0x00000030,0x030000AD,0x0000002C,
+DEBUG_REG_124:0x00000030,0x040000AD,0x0000002C,
+DEBUG_REG_125:0x00000030,0x050000AD,0x0000002C,
+DEBUG_REG_126:0x00000030,0x060000AD,0x0000002C,
+DEBUG_REG_127:0x00000030,0x070000AD,0x0000002C,
+DEBUG_REG_128:0x00000030,0x080000AD,0x0000002C,
+DEBUG_REG_129:0x00000030,0x090000AD,0x0000002C,
+DEBUG_REG_130:0x00000030,0x0a0000AD,0x0000002C,
+DEBUG_REG_131:0x00000030,0x0b0000AD,0x0000002C,
+DEBUG_REG_132:0x00000030,0x0c0000AD,0x0000002C,
+DEBUG_REG_133:0x00000030,0x0d0000AD,0x0000002C,
+DEBUG_REG_134:0x00000030,0x0e0000AD,0x0000002C,
+DEBUG_REG_135:0x00000030,0x0f0000AD,0x0000002C,
+DEBUG_REG_136:0x00000030,0x100000AD,0x0000002C,
+DEBUG_REG_137:0x00000030,0x110000AD,0x0000002C,
+DEBUG_REG_138:0x00000030,0x120000AD,0x0000002C,
+DEBUG_REG_139:0x00000030,0x130000AD,0x0000002C,
+DEBUG_REG_140:0x00000030,0x140000AD,0x0000002C,
+DEBUG_REG_141:0x00000030,0x150000AD,0x0000002C,
+DEBUG_REG_142:0x00000030,0x160000AD,0x0000002C,
+DEBUG_REG_143:0x00000030,0x170000AD,0x0000002C,
+DEBUG_REG_144:0x00000030,0x180000AD,0x0000002C,
+DEBUG_REG_145:0x00000030,0x190000AD,0x0000002C,
+DEBUG_REG_146:0x00000030,0x1a0000AD,0x0000002C,
+DEBUG_REG_147:0x00000030,0x1b0000AD,0x0000002C,
+DEBUG_REG_148:0x00000030,0x1c0000AD,0x0000002C,
+DEBUG_REG_149:0x00000030,0x1d0000AD,0x0000002C,
+DEBUG_REG_150:0x00000030,0x1e0000AD,0x0000002C,
+DEBUG_REG_151:0x00000030,0x1f0000AD,0x0000002C,
+DEBUG_REG_152:0x00000030,0x200000AD,0x0000002C,
+DEBUG_REG_153:0x00000030,0x210000AD,0x0000002C,
+DEBUG_REG_154:0x00000030,0x220000AD,0x0000002C,
+DEBUG_REG_155:0x00000030,0x230000AD,0x0000002C,
+DEBUG_REG_156:0x00000030,0x240000AD,0x0000002C,
+DEBUG_REG_157:0x00000030,0x250000AD,0x0000002C,
+
+# 3 bpf0_mcusys debug flag dump
+DEBUG_REG_158:0x00000044,0x00000000,
+DEBUG_REG_159:0x00000030,0x000201A9,0x0000002C,
+DEBUG_REG_160:0x00000030,0x000403A9,0x0000002C,
+DEBUG_REG_161:0x00000030,0x000605A9,0x0000002C,
+DEBUG_REG_162:0x00000030,0x000807A9,0x0000002C,
+DEBUG_REG_163:0x00000030,0x000A09A9,0x0000002C,
+DEBUG_REG_164:0x00000030,0x000C0BA9,0x0000002C,
+DEBUG_REG_165:0x00000030,0x000E0DA9,0x0000002C,
+DEBUG_REG_166:0x00000030,0x00100FA9,0x0000002C,
+DEBUG_REG_167:0x00000030,0x001211A9,0x0000002C,
+DEBUG_REG_168:0x00000030,0x001413A9,0x0000002C,
+DEBUG_REG_169:0x00000030,0x001615A9,0x0000002C,
+DEBUG_REG_170:0x00000030,0x001817A9,0x0000002C,
+DEBUG_REG_171:0x00000030,0x001A19A9,0x0000002C,
+DEBUG_REG_172:0x00000030,0x001C1BA9,0x0000002C,
+DEBUG_REG_173:0x00000030,0x001E1DA9,0x0000002C,
+DEBUG_REG_174:0x00000030,0x00201FA9,0x0000002C,
+DEBUG_REG_175:0x00000030,0x002221A9,0x0000002C,
+DEBUG_REG_176:0x00000030,0x002423A9,0x0000002C,
+DEBUG_REG_177:0x00000030,0x002625A9,0x0000002C,
+DEBUG_REG_178:0x00000030,0x002827A9,0x0000002C,
+DEBUG_REG_179:0x00000030,0x002A29A9,0x0000002C,
+DEBUG_REG_180:0x00000030,0x002C2BA9,0x0000002C,
+DEBUG_REG_181:0x00000030,0x002E2DA9,0x0000002C,
+DEBUG_REG_182:0x00000030,0x00302FA9,0x0000002C,
+DEBUG_REG_183:0x00000030,0x003231A9,0x0000002C,
+DEBUG_REG_184:0x00000030,0x003433A9,0x0000002C,
+DEBUG_REG_185:0x00000030,0x003635A9,0x0000002C,
+DEBUG_REG_186:0x00000030,0x003837A9,0x0000002C,
+DEBUG_REG_187:0x00000030,0x003A39A9,0x0000002C,
+DEBUG_REG_188:0x00000030,0x003C3BA9,0x0000002C,
+DEBUG_REG_189:0x00000030,0x003E3DA9,0x0000002C,
+DEBUG_REG_190:0x00000030,0x00403FA9,0x0000002C,
+DEBUG_REG_191:0x00000030,0x004241A9,0x0000002C,
+DEBUG_REG_192:0x00000030,0x004443A9,0x0000002C,
+DEBUG_REG_193:0x00000030,0x004645A9,0x0000002C,
+DEBUG_REG_194:0x00000030,0x004847A9,0x0000002C,
+DEBUG_REG_195:0x00000030,0x004A49A9,0x0000002C,
+DEBUG_REG_196:0x00000030,0x004C4BA9,0x0000002C,
+DEBUG_REG_197:0x00000030,0x004E4DA9,0x0000002C,
+DEBUG_REG_198:0x00000030,0x00504FA9,0x0000002C,
+DEBUG_REG_199:0x00000030,0x005251A9,0x0000002C,
+DEBUG_REG_200:0x00000030,0x005453A9,0x0000002C,
+DEBUG_REG_201:0x00000030,0x005655A9,0x0000002C,
+DEBUG_REG_202:0x00000030,0x005857A9,0x0000002C,
+DEBUG_REG_203:0x00000030,0x005A59A9,0x0000002C,
+DEBUG_REG_204:0x00000030,0x005C5BA9,0x0000002C,
+DEBUG_REG_205:0x00000030,0x005E5DA9,0x0000002C,
+DEBUG_REG_206:0x00000030,0x00605FA9,0x0000002C,
+DEBUG_REG_207:0x00000030,0x006261A9,0x0000002C,
+DEBUG_REG_208:0x00000030,0x006463A9,0x0000002C,
+DEBUG_REG_209:0x00000030,0x006665A9,0x0000002C,
+DEBUG_REG_210:0x00000030,0x006867A9,0x0000002C,
+DEBUG_REG_211:0x00000030,0x006A69A9,0x0000002C,
+DEBUG_REG_212:0x00000030,0x006C6BA9,0x0000002C,
+DEBUG_REG_213:0x00000030,0x006E6DA9,0x0000002C,
+DEBUG_REG_214:0x00000030,0x00706FA9,0x0000002C,
+DEBUG_REG_215:0x00000030,0x007271A9,0x0000002C,
+DEBUG_REG_216:0x00000030,0x007473A9,0x0000002C,
+DEBUG_REG_217:0x00000030,0x007675A9,0x0000002C,
+DEBUG_REG_218:0x00000030,0x007877A9,0x0000002C,
+
+# 4 bbpf0 vdesigner bus hang dump
+DEBUG_REG_219:0x00000044,0x00000000,
+DEBUG_REG_220:0x00000030,0x104A49A9,0x0000002C,
+DEBUG_REG_221:0x00000030,0x104C4BA9,0x0000002C,
+DEBUG_REG_222:0x00000030,0x204A49A9,0x0000002C,
+DEBUG_REG_223:0x00000030,0x204C4BA9,0x0000002C,
+DEBUG_REG_224:0x00000030,0x304A49A9,0x0000002C,
+DEBUG_REG_225:0x00000030,0x304C4BA9,0x0000002C,
+DEBUG_REG_226:0x00000030,0x404A49A9,0x0000002C,
+DEBUG_REG_227:0x00000030,0x404C4BA9,0x0000002C,
+DEBUG_REG_228:0x00000030,0x504A49A9,0x0000002C,
+DEBUG_REG_229:0x00000030,0x504C4BA9,0x0000002C,
+DEBUG_REG_230:0x00000030,0x604A49A9,0x0000002C,
+DEBUG_REG_231:0x00000030,0x604C4BA9,0x0000002C,
+DEBUG_REG_232:0x00000030,0x704A49A9,0x0000002C,
+DEBUG_REG_233:0x00000030,0x704C4BA9,0x0000002C,
+DEBUG_REG_234:0x00000030,0x804A49A9,0x0000002C,
+DEBUG_REG_235:0x00000030,0x804C4BA9,0x0000002C,
+DEBUG_REG_236:0x00000030,0x904A49A9,0x0000002C,
+DEBUG_REG_237:0x00000030,0x904C4BA9,0x0000002C,
+DEBUG_REG_238:0x00000030,0xa04A49A9,0x0000002C,
+DEBUG_REG_239:0x00000030,0xa04C4BA9,0x0000002C,
+DEBUG_REG_240:0x00000030,0xb04A49A9,0x0000002C,
+DEBUG_REG_241:0x00000030,0xb04C4BA9,0x0000002C,
+
+DEBUG_REG_NUM:242,
\ No newline at end of file
diff --git a/debug_sop_file/usb_debug_7902.bin b/debug_sop_file/usb_debug_7902.bin
new file mode 100644
index 0000000..51a7086
--- /dev/null
+++ b/debug_sop_file/usb_debug_7902.bin
@@ -0,0 +1,245 @@
+# File path depends on kernel request_firmware API, EX:etcfirmware orlibfirmware
+# Format:
+# 1. ":" is necessary between name & command
+# 2. HEX need prefix '0x'
+# 3. Each HEX end need ','
+# 4. command len means
+# a.len is 1 :read reg address
+# b.len is 2 :write reg address and value
+# c.len is 3 :write reg address value and read
+
+# BGF MCUSYS debug
+DEBUG_REG_000:0x74000A0C,0x000200A9,0x74000A08,
+DEBUG_REG_001:0x74000A0C,0x000402A9,0x74000A08,
+DEBUG_REG_002:0x74000A0C,0x000604A9,0x74000A08,
+DEBUG_REG_003:0x74000A0C,0x000806A9,0x74000A08,
+DEBUG_REG_004:0x74000A0C,0x000A08A9,0x74000A08,
+DEBUG_REG_005:0x74000A0C,0x000C0AA9,0x74000A08,
+DEBUG_REG_006:0x74000A0C,0x000E0CA9,0x74000A08,
+DEBUG_REG_007:0x74000A0C,0x00100EA9,0x74000A08,
+DEBUG_REG_008:0x74000A0C,0x001210A9,0x74000A08,
+DEBUG_REG_009:0x74000A0C,0x001412A9,0x74000A08,
+DEBUG_REG_010:0x74000A0C,0x001614A9,0x74000A08,
+DEBUG_REG_011:0x74000A0C,0x001816A9,0x74000A08,
+DEBUG_REG_012:0x74000A0C,0x001A18A9,0x74000A08,
+DEBUG_REG_013:0x74000A0C,0x001C1AA9,0x74000A08,
+DEBUG_REG_014:0x74000A0C,0x001E1CA9,0x74000A08,
+DEBUG_REG_015:0x74000A0C,0x00201EA9,0x74000A08,
+DEBUG_REG_016:0x74000A0C,0x002220A9,0x74000A08,
+DEBUG_REG_017:0x74000A0C,0x002422A9,0x74000A08,
+DEBUG_REG_018:0x74000A0C,0x002624A9,0x74000A08,
+DEBUG_REG_019:0x74000A0C,0x002826A9,0x74000A08,
+DEBUG_REG_020:0x74000A0C,0x002A28A9,0x74000A08,
+DEBUG_REG_021:0x74000A0C,0x002C2AA9,0x74000A08,
+DEBUG_REG_022:0x74000A0C,0x002E2CA9,0x74000A08,
+DEBUG_REG_023:0x74000A0C,0x00302EA9,0x74000A08,
+DEBUG_REG_024:0x74000A0C,0x003230A9,0x74000A08,
+DEBUG_REG_025:0x74000A0C,0x003432A9,0x74000A08,
+DEBUG_REG_026:0x74000A0C,0x003634A9,0x74000A08,
+DEBUG_REG_027:0x74000A0C,0x003836A9,0x74000A08,
+DEBUG_REG_028:0x74000A0C,0x003A38A9,0x74000A08,
+DEBUG_REG_029:0x74000A0C,0x003C3AA9,0x74000A08,
+DEBUG_REG_030:0x74000A0C,0x003E3CA9,0x74000A08,
+DEBUG_REG_031:0x74000A0C,0x00403EA9,0x74000A08,
+DEBUG_REG_032:0x74000A0C,0x004240A9,0x74000A08,
+DEBUG_REG_033:0x74000A0C,0x004442A9,0x74000A08,
+DEBUG_REG_034:0x74000A0C,0x004644A9,0x74000A08,
+DEBUG_REG_035:0x74000A0C,0x004846A9,0x74000A08,
+DEBUG_REG_036:0x74000A0C,0x004A48A9,0x74000A08,
+DEBUG_REG_037:0x74000A0C,0x004C4AA9,0x74000A08,
+DEBUG_REG_038:0x74000A0C,0x004E4CA9,0x74000A08,
+DEBUG_REG_039:0x74000A0C,0x00504EA9,0x74000A08,
+DEBUG_REG_040:0x74000A0C,0x005250A9,0x74000A08,
+DEBUG_REG_041:0x74000A0C,0x005452A9,0x74000A08,
+DEBUG_REG_042:0x74000A0C,0x005654A9,0x74000A08,
+DEBUG_REG_043:0x74000A0C,0x005856A9,0x74000A08,
+DEBUG_REG_044:0x74000A0C,0x005A58A9,0x74000A08,
+DEBUG_REG_045:0x74000A0C,0x005C5AA9,0x74000A08,
+DEBUG_REG_046:0x74000A0C,0x005E5CA9,0x74000A08,
+DEBUG_REG_047:0x74000A0C,0x00605EA9,0x74000A08,
+DEBUG_REG_048:0x74000A0C,0x006260A9,0x74000A08,
+DEBUG_REG_049:0x74000A0C,0x006462A9,0x74000A08,
+DEBUG_REG_050:0x74000A0C,0x006664A9,0x74000A08,
+DEBUG_REG_051:0x74000A0C,0x006866A9,0x74000A08,
+DEBUG_REG_052:0x74000A0C,0x006A68A9,0x74000A08,
+DEBUG_REG_053:0x74000A0C,0x006C6AA9,0x74000A08,
+DEBUG_REG_054:0x74000A0C,0x006E6CA9,0x74000A08,
+DEBUG_REG_055:0x74000A0C,0x00706EA9,0x74000A08,
+DEBUG_REG_056:0x74000A0C,0x007270A9,0x74000A08,
+DEBUG_REG_057:0x74000A0C,0x007472A9,0x74000A08,
+DEBUG_REG_058:0x74000A0C,0x007674A9,0x74000A08,
+DEBUG_REG_059:0x74000A0C,0x007876A9,0x74000A08,
+DEBUG_REG_060:0x74000A0C,0x007A78A9,0x74000A08,
+DEBUG_REG_061:0x74000A0C,0x007C7AA9,0x74000A08,
+DEBUG_REG_062:0x74000A0C,0x007E7CA9,0x74000A08,
+DEBUG_REG_063:0x74000A0C,0x00807EA9,0x74000A08,
+DEBUG_REG_064:0x74000A0C,0x008280A9,0x74000A08,
+DEBUG_REG_065:0x74000A0C,0x008482A9,0x74000A08,
+DEBUG_REG_066:0x74000A0C,0x008684A9,0x74000A08,
+DEBUG_REG_067:0x74000A0C,0x008886A9,0x74000A08,
+DEBUG_REG_068:0x74000A0C,0x008A88A9,0x74000A08,
+DEBUG_REG_069:0x74000A0C,0x008C8AA9,0x74000A08,
+DEBUG_REG_070:0x74000A0C,0x008E8CA9,0x74000A08,
+DEBUG_REG_071:0x74000A0C,0x00908EA9,0x74000A08,
+DEBUG_REG_072:0x74000A0C,0x009290A9,0x74000A08,
+DEBUG_REG_073:0x74000A0C,0x009492A9,0x74000A08,
+DEBUG_REG_074:0x74000A0C,0x009694A9,0x74000A08,
+DEBUG_REG_075:0x74000A0C,0x009896A9,0x74000A08,
+DEBUG_REG_076:0x74000A0C,0x009A98A9,0x74000A08,
+DEBUG_REG_077:0x74000A0C,0x009C9AA9,0x74000A08,
+DEBUG_REG_078:0x74000A0C,0x009E9CA9,0x74000A08,
+DEBUG_REG_079:0x74000A0C,0x00A09EA9,0x74000A08,
+DEBUG_REG_080:0x74000A0C,0x00A2A0A9,0x74000A08,
+DEBUG_REG_081:0x74000A0C,0x00A4A2A9,0x74000A08,
+DEBUG_REG_082:0x74000A0C,0x00A6A4A9,0x74000A08,
+DEBUG_REG_083:0x74000A0C,0x00A8A6A9,0x74000A08,
+DEBUG_REG_084:0x74000A0C,0x00AAA8A9,0x74000A08,
+DEBUG_REG_085:0x74000A0C,0x00ACAAA9,0x74000A08,
+DEBUG_REG_086:0x74000A0C,0x00AEACA9,0x74000A08,
+DEBUG_REG_087:0x74000A0C,0x00B0AEA9,0x74000A08,
+DEBUG_REG_088:0x74000A0C,0x00B2B0A9,0x74000A08,
+DEBUG_REG_089:0x74000A0C,0x00B4B2A9,0x74000A08,
+
+# BGF BUS debug
+DEBUG_REG_090:0x74000A0C,0x104A48A9,0x74000A08,
+DEBUG_REG_091:0x74000A0C,0x104C4AA9,0x74000A08,
+DEBUG_REG_092:0x74000A0C,0x204A48A9,0x74000A08,
+DEBUG_REG_093:0x74000A0C,0x204C4AA9,0x74000A08,
+DEBUG_REG_094:0x74000A0C,0x304A48A9,0x74000A08,
+DEBUG_REG_095:0x74000A0C,0x304C4AA9,0x74000A08,
+DEBUG_REG_096:0x74000A0C,0x404A48A9,0x74000A08,
+DEBUG_REG_097:0x74000A0C,0x404C4AA9,0x74000A08,
+DEBUG_REG_098:0x74000A0C,0x504A48A9,0x74000A08,
+DEBUG_REG_099:0x74000A0C,0x504C4AA9,0x74000A08,
+DEBUG_REG_100:0x74000A0C,0x604A48A9,0x74000A08,
+DEBUG_REG_101:0x74000A0C,0x604C4AA9,0x74000A08,
+DEBUG_REG_102:0x74000A0C,0x704A48A9,0x74000A08,
+DEBUG_REG_103:0x74000A0C,0x704C4AA9,0x74000A08,
+DEBUG_REG_104:0x74000A0C,0x804A48A9,0x74000A08,
+DEBUG_REG_105:0x74000A0C,0x804C4AA9,0x74000A08,
+DEBUG_REG_106:0x74000A0C,0x904A48A9,0x74000A08,
+DEBUG_REG_107:0x74000A0C,0x904C4AA9,0x74000A08,
+DEBUG_REG_108:0x74000A0C,0xA04A48A9,0x74000A08,
+DEBUG_REG_109:0x74000A0C,0xA04C4AA9,0x74000A08,
+
+# BGF PC debug
+DEBUG_REG_110:0x74000A0C,0x1E000001,0x74000A08,
+DEBUG_REG_111:0x74000A0C,0x1E000005,0x74000A08,
+DEBUG_REG_112:0x74000A0C,0x1E000009,0x74000A08,
+DEBUG_REG_113:0x74000A0C,0x1E00000D,0x74000A08,
+DEBUG_REG_114:0x74000A0C,0x1E000011,0x74000A08,
+DEBUG_REG_115:0x74000A0C,0x1E000015,0x74000A08,
+DEBUG_REG_116:0x74000A0C,0x1E000019,0x74000A08,
+DEBUG_REG_117:0x74000A0C,0x1E00001D,0x74000A08,
+DEBUG_REG_118:0x74000A0C,0x1E000021,0x74000A08,
+DEBUG_REG_119:0x74000A0C,0x1E000025,0x74000A08,
+DEBUG_REG_120:0x74000A0C,0x1E000029,0x74000A08,
+DEBUG_REG_121:0x74000A0C,0x1E00002D,0x74000A08,
+DEBUG_REG_122:0x74000A0C,0x1E000031,0x74000A08,
+DEBUG_REG_123:0x74000A0C,0x1E000035,0x74000A08,
+DEBUG_REG_124:0x74000A0C,0x1E000039,0x74000A08,
+DEBUG_REG_125:0x74000A0C,0x1E00003D,0x74000A08,
+DEBUG_REG_126:0x74000A0C,0x1E000041,0x74000A08,
+DEBUG_REG_127:0x74000A0C,0x1E000045,0x74000A08,
+DEBUG_REG_128:0x74000A0C,0x1E000049,0x74000A08,
+DEBUG_REG_129:0x74000A0C,0x1E00004D,0x74000A08,
+DEBUG_REG_130:0x74000A0C,0x1E000051,0x74000A08,
+DEBUG_REG_131:0x74000A0C,0x1E000055,0x74000A08,
+DEBUG_REG_132:0x74000A0C,0x1E000059,0x74000A08,
+DEBUG_REG_133:0x74000A0C,0x1E00005D,0x74000A08,
+DEBUG_REG_134:0x74000A0C,0x1E000061,0x74000A08,
+DEBUG_REG_135:0x74000A0C,0x1E000065,0x74000A08,
+DEBUG_REG_136:0x74000A0C,0x1E000069,0x74000A08,
+DEBUG_REG_137:0x74000A0C,0x1E00006D,0x74000A08,
+DEBUG_REG_138:0x74000A0C,0x1E000071,0x74000A08,
+DEBUG_REG_139:0x74000A0C,0x1E000075,0x74000A08,
+DEBUG_REG_140:0x74000A0C,0x1E000079,0x74000A08,
+DEBUG_REG_141:0x74000A0C,0x1E00007D,0x74000A08,
+DEBUG_REG_142:0x74000A0C,0x1E000081,0x74000A08,
+DEBUG_REG_143:0x74000A0C,0x1E000085,0x74000A08,
+DEBUG_REG_144:0x74000A0C,0x1E000089,0x74000A08,
+DEBUG_REG_145:0x74000A0C,0x1E00008D,0x74000A08,
+DEBUG_REG_146:0x74000A0C,0x1E000091,0x74000A08,
+DEBUG_REG_147:0x74000A0C,0x1E000095,0x74000A08,
+DEBUG_REG_148:0x74000A0C,0x1E000099,0x74000A08,
+DEBUG_REG_149:0x74000A0C,0x1E00009D,0x74000A08,
+DEBUG_REG_150:0x74000A0C,0x1E0000A1,0x74000A08,
+DEBUG_REG_151:0x74000A0C,0x1E0000A5,0x74000A08,
+DEBUG_REG_152:0x74000A0C,0x1E0000A9,0x74000A08,
+DEBUG_REG_153:0x74000A0C,0x1E0000AD,0x74000A08,
+DEBUG_REG_154:0x74000A0C,0x1E0000B1,0x74000A08,
+DEBUG_REG_155:0x74000A0C,0x1E000101,0x74000A08,
+DEBUG_REG_156:0x74000A0C,0x1E000105,0x74000A08,
+DEBUG_REG_157:0x74000A0C,0x1E000109,0x74000A08,
+DEBUG_REG_158:0x74000A0C,0x1E00010D,0x74000A08,
+DEBUG_REG_159:0x74000A0C,0x1E000111,0x74000A08,
+DEBUG_REG_160:0x74000A0C,0x1E000115,0x74000A08,
+DEBUG_REG_161:0x74000A0C,0x1E000119,0x74000A08,
+DEBUG_REG_162:0x74000A0C,0x1E00011D,0x74000A08,
+DEBUG_REG_163:0x74000A0C,0x1E000121,0x74000A08,
+DEBUG_REG_164:0x74000A0C,0x1E000125,0x74000A08,
+DEBUG_REG_165:0x74000A0C,0x1E000129,0x74000A08,
+DEBUG_REG_166:0x74000A0C,0x1E00012D,0x74000A08,
+DEBUG_REG_167:0x74000A0C,0x1E000131,0x74000A08,
+DEBUG_REG_168:0x74000A0C,0x1E000135,0x74000A08,
+DEBUG_REG_169:0x74000A0C,0x1E000139,0x74000A08,
+DEBUG_REG_170:0x74000A0C,0x1E00013D,0x74000A08,
+DEBUG_REG_171:0x74000A0C,0x1E000141,0x74000A08,
+DEBUG_REG_172:0x74000A0C,0x1E000145,0x74000A08,
+DEBUG_REG_173:0x74000A0C,0x1E000149,0x74000A08,
+DEBUG_REG_174:0x74000A0C,0x1E00014D,0x74000A08,
+DEBUG_REG_175:0x74000A0C,0x1E000151,0x74000A08,
+DEBUG_REG_176:0x74000A0C,0x1E000155,0x74000A08,
+DEBUG_REG_177:0x74000A0C,0x1E000159,0x74000A08,
+DEBUG_REG_178:0x74000A0C,0x1E00015D,0x74000A08,
+DEBUG_REG_179:0x74000A0C,0x1E000161,0x74000A08,
+DEBUG_REG_180:0x74000A0C,0x1E000165,0x74000A08,
+DEBUG_REG_181:0x74000A0C,0x1E000169,0x74000A08,
+DEBUG_REG_182:0x74000A0C,0x1E00016D,0x74000A08,
+DEBUG_REG_183:0x74000A0C,0x1E000171,0x74000A08,
+DEBUG_REG_184:0x74000A0C,0x1E000175,0x74000A08,
+DEBUG_REG_185:0x74000A0C,0x1E000179,0x74000A08,
+DEBUG_REG_186:0x74000A0C,0x1E00017D,0x74000A08,
+DEBUG_REG_187:0x74000A0C,0x1E000181,0x74000A08,
+
+# BGF MCU core debug
+DEBUG_REG_188:0x74000A0C,0x000000AD,0x74000A08,
+DEBUG_REG_189:0x74000A0C,0x010000AD,0x74000A08,
+DEBUG_REG_190:0x74000A0C,0x020000AD,0x74000A08,
+DEBUG_REG_191:0x74000A0C,0x030000AD,0x74000A08,
+DEBUG_REG_192:0x74000A0C,0x040000AD,0x74000A08,
+DEBUG_REG_193:0x74000A0C,0x050000AD,0x74000A08,
+DEBUG_REG_194:0x74000A0C,0x060000AD,0x74000A08,
+DEBUG_REG_195:0x74000A0C,0x070000AD,0x74000A08,
+DEBUG_REG_196:0x74000A0C,0x080000AD,0x74000A08,
+DEBUG_REG_197:0x74000A0C,0x090000AD,0x74000A08,
+DEBUG_REG_198:0x74000A0C,0x0a0000AD,0x74000A08,
+DEBUG_REG_199:0x74000A0C,0x0b0000AD,0x74000A08,
+DEBUG_REG_200:0x74000A0C,0x0c0000AD,0x74000A08,
+DEBUG_REG_201:0x74000A0C,0x0d0000AD,0x74000A08,
+DEBUG_REG_202:0x74000A0C,0x0e0000AD,0x74000A08,
+DEBUG_REG_203:0x74000A0C,0x0f0000AD,0x74000A08,
+DEBUG_REG_204:0x74000A0C,0x100000AD,0x74000A08,
+DEBUG_REG_205:0x74000A0C,0x110000AD,0x74000A08,
+DEBUG_REG_206:0x74000A0C,0x120000AD,0x74000A08,
+DEBUG_REG_207:0x74000A0C,0x130000AD,0x74000A08,
+DEBUG_REG_208:0x74000A0C,0x140000AD,0x74000A08,
+DEBUG_REG_209:0x74000A0C,0x150000AD,0x74000A08,
+DEBUG_REG_210:0x74000A0C,0x160000AD,0x74000A08,
+DEBUG_REG_211:0x74000A0C,0x170000AD,0x74000A08,
+DEBUG_REG_212:0x74000A0C,0x180000AD,0x74000A08,
+DEBUG_REG_213:0x74000A0C,0x190000AD,0x74000A08,
+DEBUG_REG_214:0x74000A0C,0x1a0000AD,0x74000A08,
+DEBUG_REG_215:0x74000A0C,0x1b0000AD,0x74000A08,
+DEBUG_REG_216:0x74000A0C,0x1c0000AD,0x74000A08,
+DEBUG_REG_217:0x74000A0C,0x1d0000AD,0x74000A08,
+DEBUG_REG_218:0x74000A0C,0x1e0000AD,0x74000A08,
+DEBUG_REG_219:0x74000A0C,0x1f0000AD,0x74000A08,
+DEBUG_REG_220:0x74000A0C,0x200000AD,0x74000A08,
+DEBUG_REG_221:0x74000A0C,0x210000AD,0x74000A08,
+DEBUG_REG_222:0x74000A0C,0x220000AD,0x74000A08,
+DEBUG_REG_223:0x74000A0C,0x230000AD,0x74000A08,
+DEBUG_REG_224:0x74000A0C,0x240000AD,0x74000A08,
+DEBUG_REG_225:0x74000A0C,0x250000AD,0x74000A08,
+
+DEBUG_REG_NUM:226,
diff --git a/debug_sop_file/usb_debug_7961.bin b/debug_sop_file/usb_debug_7961.bin
new file mode 100644
index 0000000..065ef37
--- /dev/null
+++ b/debug_sop_file/usb_debug_7961.bin
@@ -0,0 +1,464 @@
+# File path depends on kernel request_firmware API, EX:etcfirmware orlibfirmware
+# Format:
+# 1. ":" is necessary between name & command
+# 2. HEX need prefix '0x'
+# 3. Each HEX end need ','
+# 4. command len means
+# a.len is 1 :read reg address
+# b.len is 2 :write reg address and value
+# c.len is 3 :write reg address value and read
+
+# power status cr
+DEBUG_REG_000:0x74000A24,0x9F1E0000,0x74000A20,
+
+#bgfsys sleep status
+DEBUG_REG_001:0x74000A04,0x80000080,0x74000A00,
+
+# bt bgf debug
+DEBUG_REG_002:0x74000A04,0x80000000,0x74000A00,
+DEBUG_REG_003:0x74000A04,0x91800000,0x74000A00,
+DEBUG_REG_004:0x74000A04,0x90880000,0x74000A00,
+DEBUG_REG_005:0x74000A04,0x86280080,0x74000A00,
+DEBUG_REG_006:0x74000A04,0x86280081,0x74000A00,
+DEBUG_REG_007:0x74000A04,0x86280082,0x74000A00,
+DEBUG_REG_008:0x74000A04,0x86280083,0x74000A00,
+DEBUG_REG_009:0x74000A04,0x86280084,0x74000A00,
+DEBUG_REG_010:0x74000A04,0x86280085,0x74000A00,
+DEBUG_REG_011:0x74000A04,0x86280086,0x74000A00,
+DEBUG_REG_012:0x74000A04,0x86280087,0x74000A00,
+DEBUG_REG_013:0x74000A04,0x86280088,0x74000A00,
+DEBUG_REG_014:0x74000A04,0x86280089,0x74000A00,
+DEBUG_REG_015:0x74000A04,0x8A480080,0x74000A00,
+DEBUG_REG_016:0x74000A04,0x8A480081,0x74000A00,
+DEBUG_REG_017:0x74000A04,0x8A480082,0x74000A00,
+DEBUG_REG_018:0x74000A04,0x8A480083,0x74000A00,
+DEBUG_REG_019:0x74000A04,0x8A480084,0x74000A00,
+DEBUG_REG_020:0x74000A04,0x8A480085,0x74000A00,
+DEBUG_REG_021:0x74000A04,0x8A480086,0x74000A00,
+DEBUG_REG_022:0x74000A04,0x8A480087,0x74000A00,
+DEBUG_REG_023:0x74000A04,0x8A480088,0x74000A00,
+DEBUG_REG_024:0x74000A04,0x8A480089,0x74000A00,
+DEBUG_REG_025:0x74000A04,0x80000080,0x74000A00,
+DEBUG_REG_026:0x74000A04,0x80000081,0x74000A00,
+DEBUG_REG_027:0x74000A04,0x80000082,0x74000A00,
+DEBUG_REG_028:0x74000A04,0x80000083,0x74000A00,
+DEBUG_REG_029:0x74000A04,0x80000084,0x74000A00,
+DEBUG_REG_030:0x74000A04,0x80000085,0x74000A00,
+DEBUG_REG_031:0x74000A04,0x80000086,0x74000A00,
+DEBUG_REG_032:0x74000A04,0x80000087,0x74000A00,
+DEBUG_REG_033:0x74000A04,0x80000088,0x74000A00,
+DEBUG_REG_034:0x74000A04,0x80000089,0x74000A00,
+DEBUG_REG_035:0x74000A04,0x8000008A,0x74000A00,
+DEBUG_REG_036:0x74000A04,0x8000008B,0x74000A00,
+DEBUG_REG_037:0x74000A04,0x8000008C,0x74000A00,
+DEBUG_REG_038:0x74000A04,0x8000008D,0x74000A00,
+DEBUG_REG_039:0x74000A04,0x8000008E,0x74000A00,
+DEBUG_REG_040:0x74000A04,0x8000008F,0x74000A00,
+DEBUG_REG_041:0x74000A04,0x81000080,0x74000A00,
+DEBUG_REG_042:0x74000A04,0x81000081,0x74000A00,
+DEBUG_REG_043:0x74000A04,0x81000082,0x74000A00,
+DEBUG_REG_044:0x74000A04,0x81000083,0x74000A00,
+DEBUG_REG_045:0x74000A04,0x81000084,0x74000A00,
+DEBUG_REG_046:0x74000A04,0x81000085,0x74000A00,
+DEBUG_REG_047:0x74000A04,0x81000086,0x74000A00,
+DEBUG_REG_048:0x74000A04,0x81000087,0x74000A00,
+DEBUG_REG_049:0x74000A04,0x81000088,0x74000A00,
+DEBUG_REG_050:0x74000A04,0x81000089,0x74000A00,
+DEBUG_REG_051:0x74000A04,0x8100008A,0x74000A00,
+DEBUG_REG_052:0x74000A04,0x8100008B,0x74000A00,
+DEBUG_REG_053:0x74000A04,0x8100008C,0x74000A00,
+DEBUG_REG_054:0x74000A04,0x8100008D,0x74000A00,
+DEBUG_REG_055:0x74000A04,0x8100008E,0x74000A00,
+DEBUG_REG_056:0x74000A04,0x8100008F,0x74000A00,
+
+# debug for coredump
+DEBUG_REG_057:0x74000A0C,0x00000089,0x74000A08,
+DEBUG_REG_058:0x74000A0C,0x0000008D,0x74000A08,
+DEBUG_REG_059:0x74000A0C,0x00000091,0x74000A08,
+DEBUG_REG_060:0x74000A0C,0x00000095,0x74000A08,
+DEBUG_REG_061:0x74000A0C,0x00000099,0x74000A08,
+DEBUG_REG_062:0x74000A0C,0x0000009D,0x74000A08,
+DEBUG_REG_063:0x74000A0C,0x000000A1,0x74000A08,
+DEBUG_REG_064:0x74000A0C,0x000000A5,0x74000A08,
+
+# 1 bpf0 pc log dump
+DEBUG_REG_065:0x74000A0C,0x00000001,0x0000002C,
+DEBUG_REG_066:0x74000A0C,0x00000005,0x0000002C,
+DEBUG_REG_067:0x74000A0C,0x00000009,0x0000002C,
+DEBUG_REG_068:0x74000A0C,0x0000000D,0x0000002C,
+DEBUG_REG_069:0x74000A0C,0x00000011,0x0000002C,
+DEBUG_REG_070:0x74000A0C,0x00000015,0x0000002C,
+DEBUG_REG_071:0x74000A0C,0x00000019,0x0000002C,
+DEBUG_REG_072:0x74000A0C,0x0000001D,0x0000002C,
+DEBUG_REG_073:0x74000A0C,0x00000021,0x0000002C,
+DEBUG_REG_074:0x74000A0C,0x00000025,0x0000002C,
+DEBUG_REG_075:0x74000A0C,0x00000029,0x0000002C,
+DEBUG_REG_076:0x74000A0C,0x0000002D,0x0000002C,
+DEBUG_REG_077:0x74000A0C,0x00000031,0x0000002C,
+DEBUG_REG_078:0x74000A0C,0x00000035,0x0000002C,
+DEBUG_REG_079:0x74000A0C,0x00000039,0x0000002C,
+DEBUG_REG_080:0x74000A0C,0x0000003D,0x0000002C,
+DEBUG_REG_081:0x74000A0C,0x00000041,0x0000002C,
+DEBUG_REG_082:0x74000A0C,0x00000045,0x0000002C,
+DEBUG_REG_083:0x74000A0C,0x00000049,0x0000002C,
+DEBUG_REG_084:0x74000A0C,0x0000004D,0x0000002C,
+DEBUG_REG_085:0x74000A0C,0x00000051,0x0000002C,
+DEBUG_REG_086:0x74000A0C,0x00000055,0x0000002C,
+DEBUG_REG_087:0x74000A0C,0x00000059,0x0000002C,
+DEBUG_REG_088:0x74000A0C,0x0000005D,0x0000002C,
+DEBUG_REG_089:0x74000A0C,0x00000061,0x0000002C,
+DEBUG_REG_090:0x74000A0C,0x00000065,0x0000002C,
+DEBUG_REG_091:0x74000A0C,0x00000069,0x0000002C,
+DEBUG_REG_092:0x74000A0C,0x0000006D,0x0000002C,
+DEBUG_REG_093:0x74000A0C,0x00000071,0x0000002C,
+DEBUG_REG_094:0x74000A0C,0x00000075,0x0000002C,
+DEBUG_REG_095:0x74000A0C,0x00000079,0x0000002C,
+DEBUG_REG_096:0x74000A0C,0x0000007D,0x0000002C,
+DEBUG_REG_097:0x74000A0C,0x00000081,0x0000002C,
+DEBUG_REG_098:0x74000A0C,0x00000085,0x0000002C,
+DEBUG_REG_099:0x74000A0C,0x00000089,0x0000002C,
+DEBUG_REG_100:0x74000A0C,0x0000008D,0x0000002C,
+DEBUG_REG_101:0x74000A0C,0x00000091,0x0000002C,
+DEBUG_REG_102:0x74000A0C,0x00000095,0x0000002C,
+DEBUG_REG_103:0x74000A0C,0x00000099,0x0000002C,
+DEBUG_REG_104:0x74000A0C,0x0000009D,0x0000002C,
+DEBUG_REG_105:0x74000A0C,0x000000A1,0x0000002C,
+DEBUG_REG_106:0x74000A0C,0x000000A5,0x0000002C,
+DEBUG_REG_107:0x74000A0C,0x000000A9,0x0000002C,
+DEBUG_REG_108:0x74000A0C,0x000000AD,0x0000002C,
+DEBUG_REG_109:0x74000A0C,0x000000B1,0x0000002C,
+
+# 2 bpf0 GPR dump
+DEBUG_REG_110:0x74000A0C,0x000000AD,0x74000A08,
+DEBUG_REG_111:0x74000A0C,0x010000AD,0x74000A08,
+DEBUG_REG_112:0x74000A0C,0x020000AD,0x74000A08,
+DEBUG_REG_113:0x74000A0C,0x030000AD,0x74000A08,
+DEBUG_REG_114:0x74000A0C,0x040000AD,0x74000A08,
+DEBUG_REG_115:0x74000A0C,0x050000AD,0x74000A08,
+DEBUG_REG_116:0x74000A0C,0x060000AD,0x74000A08,
+DEBUG_REG_117:0x74000A0C,0x070000AD,0x74000A08,
+DEBUG_REG_118:0x74000A0C,0x080000AD,0x74000A08,
+DEBUG_REG_119:0x74000A0C,0x090000AD,0x74000A08,
+DEBUG_REG_120:0x74000A0C,0x0A0000AD,0x74000A08,
+DEBUG_REG_121:0x74000A0C,0x0B0000AD,0x74000A08,
+DEBUG_REG_122:0x74000A0C,0x0C0000AD,0x74000A08,
+DEBUG_REG_123:0x74000A0C,0x0D0000AD,0x74000A08,
+DEBUG_REG_124:0x74000A0C,0x0E0000AD,0x74000A08,
+DEBUG_REG_125:0x74000A0C,0x0F0000AD,0x74000A08,
+DEBUG_REG_126:0x74000A0C,0x100000AD,0x74000A08,
+DEBUG_REG_127:0x74000A0C,0x110000AD,0x74000A08,
+DEBUG_REG_128:0x74000A0C,0x120000AD,0x74000A08,
+DEBUG_REG_129:0x74000A0C,0x130000AD,0x74000A08,
+DEBUG_REG_130:0x74000A0C,0x140000AD,0x74000A08,
+DEBUG_REG_131:0x74000A0C,0x150000AD,0x74000A08,
+DEBUG_REG_132:0x74000A0C,0x160000AD,0x74000A08,
+DEBUG_REG_133:0x74000A0C,0x170000AD,0x74000A08,
+DEBUG_REG_134:0x74000A0C,0x180000AD,0x74000A08,
+DEBUG_REG_135:0x74000A0C,0x190000AD,0x74000A08,
+DEBUG_REG_136:0x74000A0C,0x1A0000AD,0x74000A08,
+DEBUG_REG_137:0x74000A0C,0x1B0000AD,0x74000A08,
+DEBUG_REG_138:0x74000A0C,0x1C0000AD,0x74000A08,
+DEBUG_REG_139:0x74000A0C,0x1D0000AD,0x74000A08,
+DEBUG_REG_140:0x74000A0C,0x1E0000AD,0x74000A08,
+DEBUG_REG_141:0x74000A0C,0x1F0000AD,0x74000A08,
+DEBUG_REG_142:0x74000A0C,0x200000AD,0x74000A08,
+DEBUG_REG_143:0x74000A0C,0x210000AD,0x74000A08,
+DEBUG_REG_144:0x74000A0C,0x220000AD,0x74000A08,
+DEBUG_REG_145:0x74000A0C,0x230000AD,0x74000A08,
+DEBUG_REG_146:0x74000A0C,0x240000AD,0x74000A08,
+DEBUG_REG_147:0x74000A0C,0x250000AD,0x74000A08,
+
+# 3 bpf0_mcusys debug flag dump
+DEBUG_REG_148:0x74000A0C,0x000201A9,0x74000A08,
+DEBUG_REG_149:0x74000A0C,0x000403A9,0x74000A08,
+DEBUG_REG_150:0x74000A0C,0x000605A9,0x74000A08,
+DEBUG_REG_151:0x74000A0C,0x000807A9,0x74000A08,
+DEBUG_REG_152:0x74000A0C,0x000A09A9,0x74000A08,
+DEBUG_REG_153:0x74000A0C,0x000C0BA9,0x74000A08,
+DEBUG_REG_154:0x74000A0C,0x000E0DA9,0x74000A08,
+DEBUG_REG_155:0x74000A0C,0x00100FA9,0x74000A08,
+DEBUG_REG_156:0x74000A0C,0x001211A9,0x74000A08,
+DEBUG_REG_157:0x74000A0C,0x001413A9,0x74000A08,
+DEBUG_REG_158:0x74000A0C,0x001615A9,0x74000A08,
+DEBUG_REG_159:0x74000A0C,0x001817A9,0x74000A08,
+DEBUG_REG_160:0x74000A0C,0x001A19A9,0x74000A08,
+DEBUG_REG_161:0x74000A0C,0x001C1BA9,0x74000A08,
+DEBUG_REG_162:0x74000A0C,0x001E1DA9,0x74000A08,
+DEBUG_REG_163:0x74000A0C,0x00201FA9,0x74000A08,
+DEBUG_REG_164:0x74000A0C,0x002221A9,0x74000A08,
+DEBUG_REG_165:0x74000A0C,0x002423A9,0x74000A08,
+DEBUG_REG_166:0x74000A0C,0x002625A9,0x74000A08,
+DEBUG_REG_167:0x74000A0C,0x002827A9,0x74000A08,
+DEBUG_REG_168:0x74000A0C,0x002A29A9,0x74000A08,
+DEBUG_REG_169:0x74000A0C,0x002C2BA9,0x74000A08,
+DEBUG_REG_170:0x74000A0C,0x002E2DA9,0x74000A08,
+DEBUG_REG_171:0x74000A0C,0x00302FA9,0x74000A08,
+DEBUG_REG_172:0x74000A0C,0x003231A9,0x74000A08,
+DEBUG_REG_173:0x74000A0C,0x003433A9,0x74000A08,
+DEBUG_REG_174:0x74000A0C,0x003635A9,0x74000A08,
+DEBUG_REG_175:0x74000A0C,0x003837A9,0x74000A08,
+DEBUG_REG_176:0x74000A0C,0x003A39A9,0x74000A08,
+DEBUG_REG_177:0x74000A0C,0x003C3BA9,0x74000A08,
+DEBUG_REG_178:0x74000A0C,0x003E3DA9,0x74000A08,
+DEBUG_REG_179:0x74000A0C,0x00403FA9,0x74000A08,
+DEBUG_REG_180:0x74000A0C,0x204241A9,0x74000A08,
+DEBUG_REG_181:0x74000A0C,0x204443A9,0x74000A08,
+DEBUG_REG_182:0x74000A0C,0x204645A9,0x74000A08,
+DEBUG_REG_183:0x74000A0C,0x204847A9,0x74000A08,
+DEBUG_REG_184:0x74000A0C,0x204A49A9,0x74000A08,
+DEBUG_REG_185:0x74000A0C,0x204C4BA9,0x74000A08,
+DEBUG_REG_186:0x74000A0C,0x204E4DA9,0x74000A08,
+DEBUG_REG_187:0x74000A0C,0x20504FA9,0x74000A08,
+DEBUG_REG_188:0x74000A0C,0x205251A9,0x74000A08,
+DEBUG_REG_189:0x74000A0C,0x205453A9,0x74000A08,
+DEBUG_REG_190:0x74000A0C,0x205655A9,0x74000A08,
+DEBUG_REG_191:0x74000A0C,0x205857A9,0x74000A08,
+DEBUG_REG_192:0x74000A0C,0x205A59A9,0x74000A08,
+DEBUG_REG_193:0x74000A0C,0x205C5BA9,0x74000A08,
+DEBUG_REG_194:0x74000A0C,0x205E5DA9,0x74000A08,
+DEBUG_REG_195:0x74000A0C,0x20605FA9,0x74000A08,
+DEBUG_REG_196:0x74000A0C,0x206261A9,0x74000A08,
+DEBUG_REG_197:0x74000A0C,0x206463A9,0x74000A08,
+DEBUG_REG_198:0x74000A0C,0x206665A9,0x74000A08,
+DEBUG_REG_199:0x74000A0C,0x206867A9,0x74000A08,
+DEBUG_REG_200:0x74000A0C,0x206A69A9,0x74000A08,
+DEBUG_REG_201:0x74000A0C,0x206C6BA9,0x74000A08,
+DEBUG_REG_202:0x74000A0C,0x206E6DA9,0x74000A08,
+DEBUG_REG_203:0x74000A0C,0x20706FA9,0x74000A08,
+DEBUG_REG_204:0x74000A0C,0x207271A9,0x74000A08,
+DEBUG_REG_205:0x74000A0C,0x207473A9,0x74000A08,
+DEBUG_REG_206:0x74000A0C,0x207675A9,0x74000A08,
+DEBUG_REG_207:0x74000A0C,0x207877A9,0x74000A08,
+
+# 4 bpf0 vdesigner bus hang dump
+DEBUG_REG_208:0x80101000,0x00000010,
+DEBUG_REG_209:0x74000A0C,0x104A49A9,0x74000A08,
+DEBUG_REG_210:0x74000A0C,0x104C4BA9,0x74000A08,
+DEBUG_REG_211:0x74000A0C,0x204A49A9,0x74000A08,
+DEBUG_REG_212:0x74000A0C,0x204C4BA9,0x74000A08,
+DEBUG_REG_213:0x74000A0C,0x304A49A9,0x74000A08,
+DEBUG_REG_214:0x74000A0C,0x304C4BA9,0x74000A08,
+DEBUG_REG_215:0x74000A0C,0x404A49A9,0x74000A08,
+DEBUG_REG_216:0x74000A0C,0x404C4BA9,0x74000A08,
+DEBUG_REG_217:0x74000A0C,0x504A49A9,0x74000A08,
+DEBUG_REG_218:0x74000A0C,0x504C4BA9,0x74000A08,
+DEBUG_REG_219:0x74000A0C,0x604A49A9,0x74000A08,
+DEBUG_REG_220:0x74000A0C,0x604C4BA9,0x74000A08,
+DEBUG_REG_221:0x74000A0C,0x704A49A9,0x74000A08,
+DEBUG_REG_222:0x74000A0C,0x704C4BA9,0x74000A08,
+DEBUG_REG_223:0x74000A0C,0x804A49A9,0x74000A08,
+DEBUG_REG_224:0x74000A0C,0x804C4BA9,0x74000A08,
+DEBUG_REG_225:0x74000A0C,0x904A49A9,0x74000A08,
+DEBUG_REG_226:0x74000A0C,0x904C4BA9,0x74000A08,
+DEBUG_REG_227:0x74000A0C,0xA04A49A9,0x74000A08,
+DEBUG_REG_228:0x74000A0C,0xA04C4BA9,0x74000A08,
+DEBUG_REG_229:0x74000A0C,0xB04A49A9,0x74000A08,
+DEBUG_REG_230:0x74000A0C,0xB04C4BA9,0x74000A08,
+
+# 5 bpf1 pc log
+DEBUG_REG_231:0x74000A0C,0x00000003,0x74000A08,
+DEBUG_REG_232:0x74000A0C,0x00000007,0x74000A08,
+DEBUG_REG_233:0x74000A0C,0x0000000B,0x74000A08,
+DEBUG_REG_234:0x74000A0C,0x0000000F,0x74000A08,
+DEBUG_REG_235:0x74000A0C,0x00000013,0x74000A08,
+DEBUG_REG_236:0x74000A0C,0x00000017,0x74000A08,
+DEBUG_REG_237:0x74000A0C,0x0000001B,0x74000A08,
+DEBUG_REG_238:0x74000A0C,0x0000001F,0x74000A08,
+DEBUG_REG_239:0x74000A0C,0x00000023,0x74000A08,
+DEBUG_REG_240:0x74000A0C,0x00000027,0x74000A08,
+DEBUG_REG_241:0x74000A0C,0x0000002B,0x74000A08,
+DEBUG_REG_242:0x74000A0C,0x0000002F,0x74000A08,
+DEBUG_REG_243:0x74000A0C,0x00000033,0x74000A08,
+DEBUG_REG_244:0x74000A0C,0x00000037,0x74000A08,
+DEBUG_REG_245:0x74000A0C,0x0000003B,0x74000A08,
+DEBUG_REG_246:0x74000A0C,0x0000003F,0x74000A08,
+DEBUG_REG_247:0x74000A0C,0x00000043,0x74000A08,
+DEBUG_REG_248:0x74000A0C,0x00000047,0x74000A08,
+DEBUG_REG_249:0x74000A0C,0x0000004B,0x74000A08,
+DEBUG_REG_250:0x74000A0C,0x0000004F,0x74000A08,
+DEBUG_REG_251:0x74000A0C,0x00000053,0x74000A08,
+DEBUG_REG_252:0x74000A0C,0x00000057,0x74000A08,
+DEBUG_REG_253:0x74000A0C,0x0000005B,0x74000A08,
+DEBUG_REG_254:0x74000A0C,0x0000005F,0x74000A08,
+DEBUG_REG_255:0x74000A0C,0x00000063,0x74000A08,
+DEBUG_REG_256:0x74000A0C,0x00000067,0x74000A08,
+DEBUG_REG_257:0x74000A0C,0x0000006B,0x74000A08,
+DEBUG_REG_258:0x74000A0C,0x0000006F,0x74000A08,
+DEBUG_REG_259:0x74000A0C,0x00000073,0x74000A08,
+DEBUG_REG_260:0x74000A0C,0x00000077,0x74000A08,
+DEBUG_REG_261:0x74000A0C,0x0000007B,0x74000A08,
+DEBUG_REG_262:0x74000A0C,0x0000007F,0x74000A08,
+DEBUG_REG_263:0x74000A0C,0x00000083,0x74000A08,
+DEBUG_REG_264:0x74000A0C,0x00000087,0x74000A08,
+DEBUG_REG_265:0x74000A0C,0x0000008B,0x74000A08,
+DEBUG_REG_266:0x74000A0C,0x0000008F,0x74000A08,
+DEBUG_REG_267:0x74000A0C,0x00000093,0x74000A08,
+DEBUG_REG_268:0x74000A0C,0x00000097,0x74000A08,
+DEBUG_REG_269:0x74000A0C,0x0000009B,0x74000A08,
+DEBUG_REG_270:0x74000A0C,0x0000009F,0x74000A08,
+DEBUG_REG_271:0x74000A0C,0x000000A3,0x74000A08,
+DEBUG_REG_272:0x74000A0C,0x000000A7,0x74000A08,
+DEBUG_REG_273:0x74000A0C,0x000000AB,0x74000A08,
+DEBUG_REG_274:0x74000A0C,0x000000AF,0x74000A08,
+DEBUG_REG_275:0x74000A0C,0x000000B3,0x74000A08,
+
+# 6 bpf1 GPR dump
+DEBUG_REG_276:0x74000A0C,0x000000AF,0x74000A08,
+DEBUG_REG_277:0x74000A0C,0x010000AF,0x74000A08,
+DEBUG_REG_278:0x74000A0C,0x020000AF,0x74000A08,
+DEBUG_REG_279:0x74000A0C,0x030000AF,0x74000A08,
+DEBUG_REG_280:0x74000A0C,0x040000AF,0x74000A08,
+DEBUG_REG_281:0x74000A0C,0x050000AF,0x74000A08,
+DEBUG_REG_282:0x74000A0C,0x060000AF,0x74000A08,
+DEBUG_REG_283:0x74000A0C,0x070000AF,0x74000A08,
+DEBUG_REG_284:0x74000A0C,0x080000AF,0x74000A08,
+DEBUG_REG_285:0x74000A0C,0x090000AF,0x74000A08,
+DEBUG_REG_286:0x74000A0C,0x0A0000AF,0x74000A08,
+DEBUG_REG_287:0x74000A0C,0x0B0000AF,0x74000A08,
+DEBUG_REG_288:0x74000A0C,0x0C0000AF,0x74000A08,
+DEBUG_REG_289:0x74000A0C,0x0D0000AF,0x74000A08,
+DEBUG_REG_290:0x74000A0C,0x0E0000AF,0x74000A08,
+DEBUG_REG_291:0x74000A0C,0x0F0000AF,0x74000A08,
+DEBUG_REG_292:0x74000A0C,0x100000AF,0x74000A08,
+DEBUG_REG_293:0x74000A0C,0x110000AF,0x74000A08,
+DEBUG_REG_294:0x74000A0C,0x120000AF,0x74000A08,
+DEBUG_REG_295:0x74000A0C,0x130000AF,0x74000A08,
+DEBUG_REG_296:0x74000A0C,0x140000AF,0x74000A08,
+DEBUG_REG_297:0x74000A0C,0x150000AF,0x74000A08,
+DEBUG_REG_298:0x74000A0C,0x160000AF,0x74000A08,
+DEBUG_REG_299:0x74000A0C,0x170000AF,0x74000A08,
+DEBUG_REG_300:0x74000A0C,0x180000AF,0x74000A08,
+DEBUG_REG_301:0x74000A0C,0x190000AF,0x74000A08,
+DEBUG_REG_302:0x74000A0C,0x1A0000AF,0x74000A08,
+DEBUG_REG_303:0x74000A0C,0x1B0000AF,0x74000A08,
+DEBUG_REG_304:0x74000A0C,0x1C0000AF,0x74000A08,
+DEBUG_REG_305:0x74000A0C,0x1D0000AF,0x74000A08,
+DEBUG_REG_306:0x74000A0C,0x1E0000AF,0x74000A08,
+DEBUG_REG_307:0x74000A0C,0x1F0000AF,0x74000A08,
+DEBUG_REG_308:0x74000A0C,0x200000AF,0x74000A08,
+DEBUG_REG_309:0x74000A0C,0x210000AF,0x74000A08,
+DEBUG_REG_310:0x74000A0C,0x220000AF,0x74000A08,
+DEBUG_REG_311:0x74000A0C,0x230000AF,0x74000A08,
+DEBUG_REG_312:0x74000A0C,0x240000AF,0x74000A08,
+DEBUG_REG_313:0x74000A0C,0x250000AF,0x74000A08,
+
+# 7 bpf0_mcusys debug flag dump
+DEBUG_REG_314:0x74000A0C,0x000201AB,0x74000A08,
+DEBUG_REG_315:0x74000A0C,0x000403AB,0x74000A08,
+DEBUG_REG_316:0x74000A0C,0x000605AB,0x74000A08,
+DEBUG_REG_317:0x74000A0C,0x000807AB,0x74000A08,
+DEBUG_REG_318:0x74000A0C,0x000A09AB,0x74000A08,
+DEBUG_REG_319:0x74000A0C,0x000C0BAB,0x74000A08,
+DEBUG_REG_320:0x74000A0C,0x000E0DAB,0x74000A08,
+DEBUG_REG_321:0x74000A0C,0x00100FAB,0x74000A08,
+DEBUG_REG_322:0x74000A0C,0x001211AB,0x74000A08,
+DEBUG_REG_323:0x74000A0C,0x001413AB,0x74000A08,
+DEBUG_REG_324:0x74000A0C,0x001615AB,0x74000A08,
+DEBUG_REG_325:0x74000A0C,0x001817AB,0x74000A08,
+DEBUG_REG_326:0x74000A0C,0x001A19AB,0x74000A08,
+DEBUG_REG_327:0x74000A0C,0x001C1BAB,0x74000A08,
+DEBUG_REG_328:0x74000A0C,0x001E1DAB,0x74000A08,
+DEBUG_REG_329:0x74000A0C,0x00201FAB,0x74000A08,
+DEBUG_REG_330:0x74000A0C,0x002221AB,0x74000A08,
+DEBUG_REG_331:0x74000A0C,0x002423AB,0x74000A08,
+DEBUG_REG_332:0x74000A0C,0x002625AB,0x74000A08,
+DEBUG_REG_333:0x74000A0C,0x002827AB,0x74000A08,
+DEBUG_REG_334:0x74000A0C,0x002A29AB,0x74000A08,
+DEBUG_REG_335:0x74000A0C,0x002C2BAB,0x74000A08,
+DEBUG_REG_336:0x74000A0C,0x002E2DAB,0x74000A08,
+DEBUG_REG_337:0x74000A0C,0x00302FAB,0x74000A08,
+DEBUG_REG_338:0x74000A0C,0x003231AB,0x74000A08,
+DEBUG_REG_339:0x74000A0C,0x003433AB,0x74000A08,
+DEBUG_REG_340:0x74000A0C,0x003635AB,0x74000A08,
+DEBUG_REG_341:0x74000A0C,0x003837AB,0x74000A08,
+DEBUG_REG_342:0x74000A0C,0x003A39AB,0x74000A08,
+DEBUG_REG_343:0x74000A0C,0x003C3BAB,0x74000A08,
+DEBUG_REG_344:0x74000A0C,0x003E3DAB,0x74000A08,
+DEBUG_REG_345:0x74000A0C,0x00403FAB,0x74000A08,
+DEBUG_REG_346:0x74000A0C,0x204241AB,0x74000A08,
+DEBUG_REG_347:0x74000A0C,0x204443AB,0x74000A08,
+DEBUG_REG_348:0x74000A0C,0x204645AB,0x74000A08,
+DEBUG_REG_349:0x74000A0C,0x204847AB,0x74000A08,
+DEBUG_REG_350:0x74000A0C,0x204A49AB,0x74000A08,
+DEBUG_REG_351:0x74000A0C,0x204C4BAB,0x74000A08,
+DEBUG_REG_352:0x74000A0C,0x204E4DAB,0x74000A08,
+DEBUG_REG_353:0x74000A0C,0x20504FAB,0x74000A08,
+DEBUG_REG_354:0x74000A0C,0x205251AB,0x74000A08,
+DEBUG_REG_355:0x74000A0C,0x205453AB,0x74000A08,
+DEBUG_REG_356:0x74000A0C,0x205655AB,0x74000A08,
+DEBUG_REG_357:0x74000A0C,0x205857AB,0x74000A08,
+DEBUG_REG_358:0x74000A0C,0x205A59AB,0x74000A08,
+DEBUG_REG_359:0x74000A0C,0x205C5BAB,0x74000A08,
+DEBUG_REG_360:0x74000A0C,0x205E5DAB,0x74000A08,
+DEBUG_REG_361:0x74000A0C,0x20605FAB,0x74000A08,
+DEBUG_REG_362:0x74000A0C,0x206261AB,0x74000A08,
+DEBUG_REG_363:0x74000A0C,0x206463AB,0x74000A08,
+DEBUG_REG_364:0x74000A0C,0x206665AB,0x74000A08,
+DEBUG_REG_365:0x74000A0C,0x206867AB,0x74000A08,
+DEBUG_REG_366:0x74000A0C,0x206A69AB,0x74000A08,
+DEBUG_REG_367:0x74000A0C,0x206C6BAB,0x74000A08,
+DEBUG_REG_368:0x74000A0C,0x206E6DAB,0x74000A08,
+DEBUG_REG_369:0x74000A0C,0x20706FAB,0x74000A08,
+DEBUG_REG_370:0x74000A0C,0x207271AB,0x74000A08,
+DEBUG_REG_371:0x74000A0C,0x207473AB,0x74000A08,
+DEBUG_REG_372:0x74000A0C,0x207675AB,0x74000A08,
+DEBUG_REG_373:0x74000A0C,0x207877AB,0x74000A08,
+
+# 8 bpf1 vdesigner bus hang dump
+DEBUG_REG_374:0x80101000,0x00010000,
+DEBUG_REG_375:0x74000A0C,0x104A49AB,0x74000A08,
+DEBUG_REG_376:0x74000A0C,0x104C4BAB,0x74000A08,
+DEBUG_REG_377:0x74000A0C,0x204A49AB,0x74000A08,
+DEBUG_REG_378:0x74000A0C,0x204C4BAB,0x74000A08,
+DEBUG_REG_379:0x74000A0C,0x304A49AB,0x74000A08,
+DEBUG_REG_380:0x74000A0C,0x304C4BAB,0x74000A08,
+DEBUG_REG_381:0x74000A0C,0x404A49AB,0x74000A08,
+DEBUG_REG_382:0x74000A0C,0x404C4BAB,0x74000A08,
+DEBUG_REG_383:0x74000A0C,0x504A49AB,0x74000A08,
+DEBUG_REG_384:0x74000A0C,0x504C4BAB,0x74000A08,
+DEBUG_REG_385:0x74000A0C,0x604A49AB,0x74000A08,
+DEBUG_REG_386:0x74000A0C,0x604C4BAB,0x74000A08,
+DEBUG_REG_387:0x74000A0C,0x704A49AB,0x74000A08,
+DEBUG_REG_388:0x74000A0C,0x704C4BAB,0x74000A08,
+DEBUG_REG_389:0x74000A0C,0x804A49AB,0x74000A08,
+DEBUG_REG_390:0x74000A0C,0x804C4BAB,0x74000A08,
+DEBUG_REG_391:0x74000A0C,0x904A49AB,0x74000A08,
+DEBUG_REG_392:0x74000A0C,0x904C4BAB,0x74000A08,
+DEBUG_REG_393:0x74000A0C,0xA04A49AB,0x74000A08,
+DEBUG_REG_394:0x74000A0C,0xA04C4BAB,0x74000A08,
+DEBUG_REG_395:0x74000A0C,0xB04A49AB,0x74000A08,
+DEBUG_REG_396:0x74000A0C,0xB04C4BAB,0x74000A08,
+
+# 9 bpf1 bus timeout
+DEBUG_REG_397:0x74000A0C,0x007675A9,0x74000A08,
+DEBUG_REG_398:0x74000A0C,0x007877A9,0x74000A08,
+DEBUG_REG_399:0x74000A0C,0x007A79A9,0x74000A08,
+DEBUG_REG_400:0x74000A0C,0x007C7BA9,0x74000A08,
+DEBUG_REG_401:0x74000A0C,0x007E7DA9,0x74000A08,
+DEBUG_REG_402:0x74000A0C,0x00807FA9,0x74000A08,
+DEBUG_REG_403:0x74000A0C,0x008281A9,0x74000A08,
+DEBUG_REG_404:0x74000A0C,0x008483A9,0x74000A08,
+DEBUG_REG_405:0x74000A0C,0x008685A9,0x74000A08,
+DEBUG_REG_406:0x74000A0C,0x008887A9,0x74000A08,
+DEBUG_REG_407:0x74000A0C,0x008A89A9,0x74000A08,
+DEBUG_REG_408:0x74000A0C,0x008C8BA9,0x74000A08,
+DEBUG_REG_409:0x74000A0C,0x008E8DA9,0x74000A08,
+DEBUG_REG_410:0x74000A0C,0x00908FA9,0x74000A08,
+DEBUG_REG_411:0x74000A0C,0x009291A9,0x74000A08,
+DEBUG_REG_412:0x74000A0C,0x009493A9,0x74000A08,
+DEBUG_REG_413:0x74000A0C,0x009695A9,0x74000A08,
+DEBUG_REG_414:0x74000A0C,0x009897A9,0x74000A08,
+DEBUG_REG_415:0x74000A0C,0x009A99A9,0x74000A08,
+DEBUG_REG_416:0x74000A0C,0x009C9BA9,0x74000A08,
+DEBUG_REG_417:0x74000A0C,0x009E9DA9,0x74000A08,
+DEBUG_REG_418:0x74000A0C,0x00A09FA9,0x74000A08,
+DEBUG_REG_419:0x74000A0C,0x00A2A1A9,0x74000A08,
+DEBUG_REG_420:0x74000A0C,0x00A4A3A9,0x74000A08,
+DEBUG_REG_421:0x74000A0C,0x00A6A5A9,0x74000A08,
+DEBUG_REG_422:0x74000A0C,0x00A8A7A9,0x74000A08,
+DEBUG_REG_423:0x74000A0C,0x00AAA9A9,0x74000A08,
+DEBUG_REG_424:0x74000A0C,0x00ACABA9,0x74000A08,
+DEBUG_REG_425:0x74000A0C,0x00AEADA9,0x74000A08,
+DEBUG_REG_426:0x74000A0C,0x00B0AFA9,0x74000A08,
+
+DEBUG_REG_NUM:427,
\ No newline at end of file
diff --git a/include/btmtk_buffer_mode.h b/include/btmtk_buffer_mode.h
new file mode 100644
index 0000000..c207f26
--- /dev/null
+++ b/include/btmtk_buffer_mode.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef __BTMTK_BUFFER_MODE_H__
+#define __BTMTK_BUFFER_MODE_H__
+
+#include "btmtk_main.h"
+
+#define BUFFER_MODE_SWITCH_FILE "wifi.cfg"
+#define BUFFER_MODE_SWITCH_FIELD "EfuseBufferModeCal"
+#define BUFFER_MODE_CFG_FILE "EEPROM_MT%X_1.bin"
+#define EFUSE_MODE 0
+#define BIN_FILE_MODE 1
+#define AUTO_MODE 2
+
+#define SET_ADDRESS_CMD_LEN 10
+#define SET_ADDRESS_EVT_LEN 7
+#define SET_ADDRESS_CMD_PAYLOAD_OFFSET 4
+
+#define SET_RADIO_CMD_LEN 12
+#define SET_RADIO_EVT_LEN 7
+#define SET_RADIO_CMD_EDR_DEF_OFFSET 4
+#define SET_RADIO_CMD_BLE_OFFSET 8
+#define SET_RADIO_CMD_EDR_MAX_OFFSET 9
+#define SET_RADIO_CMD_EDR_MODE_OFFSET 11
+
+#define SET_GRP_CMD_LEN 13
+#define SET_GRP_EVT_LEN 7
+#define SET_GRP_CMD_PAYLOAD_OFFSET 8
+
+#define SET_PWR_OFFSET_CMD_LEN 14
+#define SET_PWR_OFFSET_EVT_LEN 7
+#define SET_PWR_OFFSET_CMD_PAYLOAD_OFFSET 8
+
+#define BUFFER_MODE_MAC_LENGTH 6
+#define BT0_MAC_OFFSET 0x139
+#define BT1_MAC_OFFSET 0x13F
+
+#define BUFFER_MODE_RADIO_LENGTH 4
+#define BT0_RADIO_OFFSET 0x145
+#define BT1_RADIO_OFFSET 0x149
+
+#define BUFFER_MODE_GROUP_LENGTH 5
+#define BT0_GROUP_ANT0_OFFSET 0x984
+#define BT0_GROUP_ANT1_OFFSET 0x9BE
+#define BT1_GROUP_ANT0_OFFSET 0x9A1
+#define BT1_GROUP_ANT1_OFFSET 0x9DB
+
+#define BUFFER_MODE_CAL_LENGTH 6
+#define BT0_CAL_ANT0_OFFSET 0x96C
+#define BT0_CAL_ANT1_OFFSET 0x9A6
+#define BT1_CAL_ANT0_OFFSET 0x989
+#define BT1_CAL_ANT1_OFFSET 0x9C3
+
+struct btmtk_buffer_mode_radio_struct {
+ u8 radio_0; /* bit 0-5:edr_init_pwr, 6-7:edr_pwr_mode */
+ u8 radio_1; /* bit 0-5:edr_max_pwr, 6-7:reserved */
+ u8 radio_2; /* bit 0-5:ble_default_pwr, 6-7:reserved */
+ u8 radio_3; /* reserved */
+};
+
+struct btmtk_buffer_mode_struct {
+ struct btmtk_dev *bdev;
+
+ unsigned char file_name[MAX_BIN_FILE_NAME_LEN];
+ int efuse_mode;
+
+ u8 bt0_mac[BUFFER_MODE_MAC_LENGTH];
+ u8 bt1_mac[BUFFER_MODE_MAC_LENGTH];
+ struct btmtk_buffer_mode_radio_struct bt0_radio;
+ struct btmtk_buffer_mode_radio_struct bt1_radio;
+ u8 bt0_ant0_grp_boundary[BUFFER_MODE_GROUP_LENGTH];
+ u8 bt0_ant1_grp_boundary[BUFFER_MODE_GROUP_LENGTH];
+ u8 bt1_ant0_grp_boundary[BUFFER_MODE_GROUP_LENGTH];
+ u8 bt1_ant1_grp_boundary[BUFFER_MODE_GROUP_LENGTH];
+ u8 bt0_ant0_pwr_offset[BUFFER_MODE_CAL_LENGTH];
+ u8 bt0_ant1_pwr_offset[BUFFER_MODE_CAL_LENGTH];
+ u8 bt1_ant0_pwr_offset[BUFFER_MODE_CAL_LENGTH];
+ u8 bt1_ant1_pwr_offset[BUFFER_MODE_CAL_LENGTH];
+};
+
+int btmtk_buffer_mode_send(struct btmtk_buffer_mode_struct *buffer_mode);
+void btmtk_buffer_mode_initialize(struct btmtk_dev *bdev, struct btmtk_buffer_mode_struct **buffer_mode);
+#endif /* __BTMTK_BUFFER_MODE_H__ */
+
diff --git a/include/btmtk_chip_if.h b/include/btmtk_chip_if.h
new file mode 100644
index 0000000..33f53f3
--- /dev/null
+++ b/include/btmtk_chip_if.h
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef __BTMTK_CHIP_IF_H__
+#define __BTMTK_CHIP_IF_H__
+
+#ifdef CHIP_IF_USB
+#include "btmtk_usb.h"
+#elif defined(CHIP_IF_SDIO)
+#include "btmtk_sdio.h"
+#elif defined(CHIP_IF_UART)
+#include "btmtk_uart.h"
+#elif defined(CHIP_IF_BTIF)
+#include "btmtk_btif.h"
+#endif
+
+int btmtk_cif_register(void);
+int btmtk_cif_deregister(void);
+
+#endif /* __BTMTK_CHIP_IF_H__ */
diff --git a/include/btmtk_chip_reset.h b/include/btmtk_chip_reset.h
new file mode 100644
index 0000000..e75688e
--- /dev/null
+++ b/include/btmtk_chip_reset.h
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef __BTMTK_CHIP_RESET_H__
+#define __BTMTK_CHIP_RESET_H__
+
+#include <linux/version.h>
+#include <linux/timer.h>
+
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_woble.h"
+
+#define CHIP_RESET_TIMEOUT 20
+
+void btmtk_reset_timer_add(struct btmtk_dev *bdev);
+void btmtk_reset_timer_update(struct btmtk_dev *bdev);
+void btmtk_reset_timer_del(struct btmtk_dev *bdev);
+void btmtk_reset_trigger(struct btmtk_dev *bdev);
+void btmtk_reset_waker(struct work_struct *work);
+
+#endif /* __BTMTK_WOBLE_H__ */
+
diff --git a/include/btmtk_define.h b/include/btmtk_define.h
new file mode 100644
index 0000000..53394d8
--- /dev/null
+++ b/include/btmtk_define.h
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef __BTMTK_DEFINE_H__
+#define __BTMTK_DEFINE_H__
+
+
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include <linux/cdev.h>
+#include <linux/spinlock.h>
+#include <linux/kallsyms.h>
+#include <linux/device.h>
+#include <asm/unaligned.h>
+
+/* Define for proce node */
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+/* Define for whole chip reset */
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/vmalloc.h>
+#include <linux/rtc.h>
+
+/** Driver version */
+#define VERSION "8.0.2022062301"
+#define SUBVER ":connac3_dev"
+
+#ifdef CFG_SUPPORT_WAKEUP_IRQ
+#define WAKEUP_BT_IRQ 1
+#else
+#define WAKEUP_BT_IRQ 0
+#endif
+
+#define ENABLESTP FALSE
+#define BTMTKUART_TX_STATE_ACTIVE 1
+#define BTMTKUART_TX_STATE_WAKEUP 2
+#define BTMTK_TX_WAIT_VND_EVT 3
+#define BTMTKUART_REQUIRED_WAKEUP 4
+#define BTMTKUART_REQUIRED_DOWNLOAD 5
+#define BTMTK_TX_SKIP_VENDOR_EVT 6
+
+#define BTMTKUART_RX_STATE_ACTIVE 1
+#define BTMTKUART_RX_STATE_WAKEUP 2
+#define BTMTKUART_RX_STATE_RESET 3
+
+#define BTMTK_DRV_OWN 0 /* init state is driver own for fw dl */
+#define BTMTK_DRV_OWNING 1
+#define BTMTK_FW_OWN 2
+#define BTMTK_FW_OWNING 3
+#define BTMTK_OWN_FAIL 4
+
+#define FW_OWN_TIMEOUT 30
+
+#define FW_OWN_TIMER_UKNOWN 0
+#define FW_OWN_TIMER_INIT 1
+#define FW_OWN_TIMER_RUNNING 2
+#define FW_OWN_TIMER_DONE 3
+
+#define BTMTK_THREAD_STOP (1 << 0)
+#define BTMTK_THREAD_TX (1 << 1)
+#define BTMTK_THREAD_RX (1 << 2)
+#define BTMTK_THREAD_FW_OWN (1 << 3)
+
+
+/**
+ * Maximum rom patch file name length
+ */
+#define MAX_BIN_FILE_NAME_LEN 64
+
+/**
+ * Type definition
+ */
+#ifndef TRUE
+ #define TRUE 1
+#endif
+#ifndef FALSE
+ #define FALSE 0
+#endif
+
+#ifndef UNUSED
+ #define UNUSED(x) (void)(x)
+#endif
+
+#ifndef ALIGN_4
+#define ALIGN_4(_value) (((_value) + 3) & ~3u)
+#endif /* ALIGN_4 */
+
+#ifndef ALIGN_8
+#define ALIGN_8(_value) (((_value) + 7) & ~7u)
+#endif /* ALIGN_4 */
+
+/* This macro check the DW alignment of the input value.
+ * _value - value of address need to check
+ */
+#ifndef IS_ALIGN_4
+#define IS_ALIGN_4(_value) (((_value) & 0x3) ? FALSE : TRUE)
+#endif /* IS_ALIGN_4 */
+
+#ifndef IS_NOT_ALIGN_4
+#define IS_NOT_ALIGN_4(_value) (((_value) & 0x3) ? TRUE : FALSE)
+#endif /* IS_NOT_ALIGN_4 */
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+
+/**
+ * Log and level definition
+ */
+#define BTMTK_LOG_LVL_ERR 1
+#define BTMTK_LOG_LVL_WARN 2
+#define BTMTK_LOG_LVL_INFO 3
+#define BTMTK_LOG_LVL_DBG 4
+#define BTMTK_LOG_LVL_MAX BTMTK_LOG_LVL_DBG
+#define BTMTK_LOG_LVL_DEF BTMTK_LOG_LVL_MAX /* default setting */
+
+#define HCI_SNOOP_ENTRY_NUM 30
+#define HCI_SNOOP_BUF_SIZE 32
+#define HCI_SNOOP_MAX_BUF_SIZE 64
+#define HCI_SNOOP_TS_STR_LEN 24
+#define WMT_OVER_HCI_HEADER_SIZE 3
+#define READ_ISO_PACKET_CMD_SIZE 4
+
+extern uint8_t btmtk_log_lvl;
+
+#define BTMTK_ERR(fmt, ...) \
+ do { if (btmtk_log_lvl >= BTMTK_LOG_LVL_ERR) pr_warn("[btmtk_err] ***"fmt"***\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_WARN(fmt, ...) \
+ do { if (btmtk_log_lvl >= BTMTK_LOG_LVL_WARN) pr_warn("[btmtk_warn] "fmt"\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_INFO(fmt, ...) \
+ do { if (btmtk_log_lvl >= BTMTK_LOG_LVL_INFO) pr_warn("[btmtk_info] "fmt"\n", ##__VA_ARGS__); } while (0)
+#define BTMTK_DBG(fmt, ...) \
+ do { if (btmtk_log_lvl >= BTMTK_LOG_LVL_DBG) pr_warn("[btmtk_dbg] "fmt"\n", ##__VA_ARGS__); } while (0)
+
+#define BTMTK_ERR_LIMITTED(fmt, ...) \
+ do { \
+ if (btmtk_log_lvl >= BTMTK_LOG_LVL_ERR) \
+ printk_ratelimited(KERN_WARNING "[btmtk_err_limit] "fmt"\n", ##__VA_ARGS__); \
+ } while (0)
+
+#define BTMTK_WARN_LIMITTED(fmt, ...) \
+ do { \
+ if (btmtk_log_lvl >= BTMTK_LOG_LVL_WARN) \
+ printk_ratelimited(KERN_WARNING "[btmtk_warn_limit] "fmt"\n", ##__VA_ARGS__); \
+ } while (0)
+
+#define BTMTK_DBG_LIMITTED(fmt, ...) \
+ do { \
+ if (btmtk_log_lvl >= BTMTK_LOG_LVL_DBG) \
+ printk_ratelimited(KERN_WARNING "[btmtk_dbg_limit] "fmt"\n", ##__VA_ARGS__); \
+ } while (0)
+
+#define BTMTK_INFO_RAW(p, l, fmt, ...) \
+ do { \
+ if (btmtk_log_lvl >= BTMTK_LOG_LVL_INFO) { \
+ int cnt_ = 0; \
+ int len_ = (l <= HCI_SNOOP_MAX_BUF_SIZE ? l : HCI_SNOOP_MAX_BUF_SIZE); \
+ uint8_t raw_buf[HCI_SNOOP_MAX_BUF_SIZE * 3 + 10]; \
+ const unsigned char *ptr = p; \
+ for (cnt_ = 0; cnt_ < len_; ++cnt_) \
+ (void)snprintf(raw_buf+3*cnt_, 4, "%02X ", ptr[cnt_]); \
+ raw_buf[3*cnt_] = '\0'; \
+ if (l <= HCI_SNOOP_MAX_BUF_SIZE) { \
+ pr_cont("[btmtk_info] "fmt" %s\n", ##__VA_ARGS__, raw_buf); \
+ } else { \
+ pr_cont("[btmtk_info] "fmt" %s (prtail)\n", ##__VA_ARGS__, raw_buf); \
+ } \
+ } \
+ } while (0)
+
+//#undef BTMTK_INFO_RAW
+
+#define BTMTK_DBG_RAW(p, l, fmt, ...) \
+ do { \
+ if (btmtk_log_lvl >= BTMTK_LOG_LVL_DBG) { \
+ int cnt_ = 0; \
+ int pos = 0; \
+ int len_ = (l <= HCI_SNOOP_MAX_BUF_SIZE ? l : HCI_SNOOP_MAX_BUF_SIZE); \
+ uint8_t raw_buf[HCI_SNOOP_MAX_BUF_SIZE * 3 + 10]; \
+ const unsigned char *ptr = p; \
+ for (pos = 0; pos < l; ) { \
+ for (cnt_ = 0; cnt_ < len_ && pos < l; ++cnt_, ++pos) { \
+ (void)snprintf(raw_buf+3*cnt_, 4, "%02X ", ptr[pos]); \
+ } \
+ raw_buf[3*cnt_] = '\0'; \
+ pr_cont("[btmtk_debug] "fmt" %s\n", ##__VA_ARGS__, raw_buf); \
+ } \
+ } \
+ } while (0)
+
+//#define BTMTK_INFO_RAW BTMTK_DBG_RAW
+
+#define BTMTK_CIF_IS_NULL(bdev, cif_event) \
+ (!bdev || !(&bdev->cif_state[cif_event]))
+
+/**
+ *
+ * HCI packet type
+ */
+#define MTK_HCI_COMMAND_PKT 0x01
+#define MTK_HCI_ACLDATA_PKT 0x02
+#define MTK_HCI_SCODATA_PKT 0x03
+#define MTK_HCI_EVENT_PKT 0x04
+#define HCI_ISO_PKT 0x05
+#define HCI_ISO_PKT_HEADER_SIZE 4
+#define HCI_ISO_PKT_WITH_ACL_HEADER_SIZE 5
+
+/**
+ * ROM patch related
+ */
+#define PATCH_HCI_HEADER_SIZE 4
+#define PATCH_WMT_HEADER_SIZE 5
+/*
+ * Enable STP
+ * HCI+WMT+STP = 4 + 5 + 1(phase) +(4=STP_HEADER + 2=CRC)
+#define PATCH_HEADER_SIZE 16
+ */
+/*#ifdef ENABLESTP
+ * #define PATCH_HEADER_SIZE (PATCH_HCI_HEADER_SIZE + PATCH_WMT_HEADER_SIZE + 1 + 6)
+ * #define UPLOAD_PATCH_UNIT 916
+ * #define PATCH_INFO_SIZE 30
+ *#else
+ */
+#define PATCH_HEADER_SIZE (PATCH_HCI_HEADER_SIZE + PATCH_WMT_HEADER_SIZE + 1)
+/* TODO, If usb use 901 patch unit size, download patch will timeout
+ * because the timeout has been set to 1s
+ */
+#define UPLOAD_PATCH_UNIT 1988
+#define PATCH_INFO_SIZE 30
+/*#endif*/
+#define PATCH_PHASE1 1
+#define PATCH_PHASE2 2
+#define PATCH_PHASE3 3
+
+/* It is for mt7961 download rom patch*/
+#define FW_ROM_PATCH_HEADER_SIZE 32
+#define FW_ROM_PATCH_GD_SIZE 64
+#define FW_ROM_PATCH_SEC_MAP_SIZE 64
+#define SEC_MAP_NEED_SEND_SIZE 52
+#define PATCH_STATUS 7
+
+#define DOWNLOAD_BY_INDEX 0
+#define DOWNLOAD_BY_TYPE 1
+#define BT_BIN_TYP_NUM 6
+
+#define IO_BUF_SIZE (HCI_MAX_EVENT_SIZE > 256 ? HCI_MAX_EVENT_SIZE : 256)
+#define EVENT_COMPARE_SIZE 64
+
+#define SECTION_SPEC_NUM 13
+
+#define BD_ADDRESS_SIZE 6
+#define PHASE1_WMT_CMD_COUNT 255
+#define VENDOR_CMD_COUNT 255
+
+#define BT_CFG_NAME "bt.cfg"
+#define BT_CFG_NAME_PREFIX "bt_mt"
+#define BT_CFG_NAME_SUFFIX "cfg"
+#define WOBLE_CFG_NAME_PREFIX "woble_setting"
+#define WOBLE_CFG_NAME_SUFFIX "bin"
+#define BT_UNIFY_WOBLE "SUPPORT_UNIFY_WOBLE"
+#define BT_UNIFY_WOBLE_TYPE "UNIFY_WOBLE_TYPE"
+#define BT_WOBLE_BY_EINT "SUPPORT_WOBLE_BY_EINT"
+#define BT_DONGLE_RESET_PIN "BT_DONGLE_RESET_GPIO_PIN"
+#define BT_RESET_DONGLE "SUPPORT_DONGLE_RESET"
+#define BT_FULL_FW_DUMP "SUPPORT_FULL_FW_DUMP"
+#define BT_WOBLE_WAKELOCK "SUPPORT_WOBLE_WAKELOCK"
+#define BT_WOBLE_FOR_BT_DISABLE "SUPPORT_WOBLE_FOR_BT_DISABLE"
+#define BT_RESET_STACK_AFTER_WOBLE "RESET_STACK_AFTER_WOBLE"
+#define BT_AUTO_PICUS "SUPPORT_AUTO_PICUS"
+#define BT_AUTO_PICUS_FILTER "PICUS_FILTER_COMMAND"
+#define BT_AUTO_PICUS_ENABLE "PICUS_ENABLE_COMMAND"
+#define BT_PICUS_TO_HOST "SUPPORT_PICUS_TO_HOST"
+#define BT_PHASE1_WMT_CMD "PHASE1_WMT_CMD"
+#define BT_VENDOR_CMD "VENDOR_CMD"
+#define BT_SINGLE_SKU "SUPPORT_BT_SINGLE_SKU"
+#define BT_AUDIO_SET "SUPPORT_BT_AUDIO_SETTING"
+#define BT_AUDIO_ENABLE_CMD "AUDIO_ENABLE_CMD"
+#define BT_AUDIO_PINMUX_NUM "AUDIO_PINMUX_NUM"
+#define BT_AUDIO_PINMUX_MODE "AUDIO_PINMUX_MODE"
+
+#define PM_KEY_BTW (0x0015) /* Notify PM the unify woble type */
+
+#define BTMTK_RESET_DOING 1
+#define BTMTK_RESET_DONE 0
+#define BTMTK_MAX_SUBSYS_RESET_COUNT 3
+
+/**
+ * Disable RESUME_RESUME
+ */
+#ifndef BT_DISABLE_RESET_RESUME
+#define BT_DISABLE_RESET_RESUME 0
+#endif
+
+enum fw_cfg_index_len {
+ FW_CFG_INX_LEN_NONE = 0,
+ FW_CFG_INX_LEN_2 = 2,
+ FW_CFG_INX_LEN_3 = 3,
+};
+
+struct fw_cfg_struct {
+ char *content; /* APCF content or radio off content */
+ u32 length; /* APCF content or radio off content of length */
+};
+
+struct bt_cfg_struct {
+ bool support_unify_woble; /* support unify woble or not */
+ bool support_woble_by_eint; /* support woble by eint or not */
+ bool support_dongle_reset; /* support chip reset or not */
+ bool support_full_fw_dump; /* dump full fw coredump or not */
+ bool support_woble_wakelock; /* support when woble error, do wakelock or not */
+ bool support_woble_for_bt_disable; /* when bt disable, support enter susend or not */
+ bool reset_stack_after_woble; /* support reset stack to re-connect IOT after resume */
+ bool support_auto_picus; /* support enable PICUS automatically */
+ struct fw_cfg_struct picus_filter; /* support on PICUS filter command customization */
+ struct fw_cfg_struct picus_enable; /* support on PICUS enable command customization */
+ bool support_picus_to_host; /* support picus log to host (boots/bluedroid) */
+ int dongle_reset_gpio_pin; /* BT_DONGLE_RESET_GPIO_PIN number */
+ unsigned int unify_woble_type; /* 0: legacy. 1: waveform. 2: IR */
+ struct fw_cfg_struct phase1_wmt_cmd[PHASE1_WMT_CMD_COUNT];
+ struct fw_cfg_struct vendor_cmd[VENDOR_CMD_COUNT];
+ bool support_bt_single_sku;
+ bool support_audio_setting; /* support audio set pinmux */
+ struct fw_cfg_struct audio_cmd; /* support on audio enable command customization */
+ struct fw_cfg_struct audio_pinmux_num; /* support on set audio pinmux num command customization */
+ struct fw_cfg_struct audio_pinmux_mode; /* support on set audio pinmux mode command customization */
+};
+
+struct bt_utc_struct {
+ struct rtc_time tm;
+ u32 usec;
+};
+
+#define BT_DOWNLOAD 1
+#define WIFI_DOWNLOAD 2
+#define ZB_DOWNLOAD 3
+
+enum debug_reg_index_len {
+ DEBUG_REG_INX_LEN_NONE = 0,
+ DEBUG_REG_INX_LEN_2 = 2,
+ DEBUG_REG_INX_LEN_3 = 3,
+};
+
+#define DEBUG_REG_SIZE 10
+#define DEBUG_REG_NUM 10
+
+struct debug_reg {
+ u32 *content;
+ u32 length;
+};
+
+struct debug_reg_struct {
+ struct debug_reg *reg;
+ u32 num;
+};
+
+#define SWAP32(x) \
+ ((u32) (\
+ (((u32) (x) & (u32) 0x000000ffUL) << 24) | \
+ (((u32) (x) & (u32) 0x0000ff00UL) << 8) | \
+ (((u32) (x) & (u32) 0x00ff0000UL) >> 8) | \
+ (((u32) (x) & (u32) 0xff000000UL) >> 24)))
+
+/* Endian byte swapping codes */
+#ifdef __LITTLE_ENDIAN
+#define cpu2le32(x) ((uint32_t)(x))
+#define le2cpu32(x) ((uint32_t)(x))
+#define cpu2be32(x) SWAP32((x))
+#define be2cpu32(x) SWAP32((x))
+#else
+#define cpu2le32(x) SWAP32((x))
+#define le2cpu32(x) SWAP32((x))
+#define cpu2be32(x) ((uint32_t)(x))
+#define be2cpu32(x) ((uint32_t)(x))
+#endif
+
+#define FW_VERSION 0x80021004
+#define CHIP_ID 0x70010200
+#define FLAVOR 0x70010020
+
+
+#ifndef DEBUG_LD_PATCH_TIME
+#define DEBUG_LD_PATCH_TIME 0
+#endif
+
+#ifndef DEBUG_DUMP_TIME
+#define DEBUG_DUMP_TIME 0
+#endif
+
+#define ERRNUM 0xFF
+
+#if DEBUG_DUMP_TIME
+void btmtk_getUTCtime(struct bt_utc_struct *utc);
+#define DUMP_TIME_STAMP(__str) \
+ do { \
+ struct bt_utc_struct utc; \
+ btmtk_getUTCtime(&utc); \
+ BTMTK_INFO("%s:%d, %s - DUMP_TIME_STAMP UTC: %d-%02d-%02d %02d:%02d:%02d.%06u", \
+ __func__, __LINE__, __str, \
+ utc.tm.tm_year, utc.tm.tm_mon, utc.tm.tm_mday, \
+ utc.tm.tm_hour, utc.tm.tm_min, utc.tm.tm_sec, utc.usec); \
+ } while (0)
+#else
+#define DUMP_TIME_STAMP(__str)
+#endif
+
+#endif /* __BTMTK_DEFINE_H__ */
diff --git a/include/btmtk_drv.h b/include/btmtk_drv.h
new file mode 100644
index 0000000..15ff235
--- /dev/null
+++ b/include/btmtk_drv.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef _BTMTK_DRV_H_
+#define _BTMTK_DRV_H_
+
+#include <linux/kthread.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <net/bluetooth/bluetooth.h>
+
+#define SAVE_FW_DUMP_IN_KERNEL 1
+
+#define SUPPORT_FW_DUMP 1
+#define BTM_HEADER_LEN 5
+#define BTM_UPLD_SIZE 2312
+
+#define MTK_TXDATA_SIZE 2000
+#define MTK_RXDATA_SIZE 2000
+
+/* Time to wait until Host Sleep state change in millisecond */
+#define WAIT_UNTIL_HS_STATE_CHANGED msecs_to_jiffies(5000)
+/* Time to wait for command response in millisecond */
+#define WAIT_UNTIL_CMD_RESP msecs_to_jiffies(5000)
+
+enum rdwr_status {
+ RDWR_STATUS_SUCCESS = 0,
+ RDWR_STATUS_FAILURE = 1,
+ RDWR_STATUS_DONE = 2
+};
+
+#define FW_DUMP_MAX_NAME_LEN 8
+#define FW_DUMP_HOST_READY 0xEE
+#define FW_DUMP_DONE 0xFF
+#define FW_DUMP_READ_DONE 0xFE
+
+struct memory_type_mapping {
+ u8 mem_name[FW_DUMP_MAX_NAME_LEN];
+ u8 *mem_ptr;
+ u32 mem_size;
+ u8 done_flag;
+};
+
+#define MTK_VENDOR_PKT 0xFE
+
+/* Vendor specific Bluetooth commands */
+#define BT_CMD_PSCAN_WIN_REPORT_ENABLE 0xFC03
+#define BT_CMD_ROUTE_SCO_TO_HOST 0xFC1D
+#define BT_CMD_SET_BDADDR 0xFC22
+#define BT_CMD_AUTO_SLEEP_MODE 0xFC23
+#define BT_CMD_HOST_SLEEP_CONFIG 0xFC59
+#define BT_CMD_HOST_SLEEP_ENABLE 0xFC5A
+#define BT_CMD_MODULE_CFG_REQ 0xFC5B
+#define BT_CMD_LOAD_CONFIG_DATA 0xFC61
+
+/* Sub-commands: Module Bringup/Shutdown Request/Response */
+#define MODULE_BRINGUP_REQ 0xF1
+#define MODULE_BROUGHT_UP 0x00
+#define MODULE_ALREADY_UP 0x0C
+
+#define MODULE_SHUTDOWN_REQ 0xF2
+
+/* Vendor specific Bluetooth events */
+#define BT_EVENT_AUTO_SLEEP_MODE 0x23
+#define BT_EVENT_HOST_SLEEP_CONFIG 0x59
+#define BT_EVENT_HOST_SLEEP_ENABLE 0x5A
+#define BT_EVENT_MODULE_CFG_REQ 0x5B
+#define BT_EVENT_POWER_STATE 0x20
+
+/* Bluetooth Power States */
+#define BT_PS_ENABLE 0x02
+#define BT_PS_DISABLE 0x03
+#define BT_PS_SLEEP 0x01
+
+/* Host Sleep states */
+#define HS_ACTIVATED 0x01
+#define HS_DEACTIVATED 0x00
+
+/* Power Save modes */
+#define PS_SLEEP 0x01
+#define PS_AWAKE 0x00
+
+#define BT_CAL_HDR_LEN 4
+#define BT_CAL_DATA_SIZE 28
+
+#define FW_DUMP_BUF_SIZE (1024*512)
+
+#define FW_DUMP_FILE_NAME_SIZE 64
+
+
+/* #define SAVE_FW_DUMP_IN_KERNEL 1 */
+
+/* stpbt device node */
+#define BT_NODE "stpbt"
+#define BT_DRIVER_NAME "BT_chrdev"
+
+struct btmtk_event {
+ u8 ec; /* event counter */
+ u8 length;
+ u8 data[4];
+} __packed;
+
+/* Prototype of global function */
+
+struct btmtk_private *btmtk_add_card(void *card);
+int btmtk_remove_card(struct btmtk_private *priv);
+
+void btmtk_interrupt(struct btmtk_private *priv);
+
+bool btmtk_check_evtpkt(struct btmtk_private *priv, struct sk_buff *skb);
+int btmtk_process_event(struct btmtk_private *priv, struct sk_buff *skb);
+
+int btmtk_send_module_cfg_cmd(struct btmtk_private *priv, u8 subcmd);
+int btmtk_pscan_window_reporting(struct btmtk_private *priv, u8 subcmd);
+int btmtk_send_hscfg_cmd(struct btmtk_private *priv);
+int btmtk_enable_ps(struct btmtk_private *priv);
+int btmtk_prepare_command(struct btmtk_private *priv);
+int btmtk_enable_hs(struct btmtk_private *priv);
+void btmtk_firmware_dump(struct btmtk_private *priv);
+
+#define META_BUFFER_SIZE (1024*50)
+
+struct _OSAL_UNSLEEPABLE_LOCK_ {
+ spinlock_t lock;
+ unsigned long flag;
+};
+
+struct ring_buffer {
+ struct _OSAL_UNSLEEPABLE_LOCK_ spin_lock;
+ u8 buffer[META_BUFFER_SIZE]; /* MTKSTP_BUFFER_SIZE:1024 */
+ u32 read_p; /* indicate the current read index */
+ u32 write_p; /* indicate the current write index */
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+
+#define FW_DUMP_END_EVENT "coredump end"
+
+#endif
+
+#endif
+
diff --git a/include/btmtk_fw_log.h b/include/btmtk_fw_log.h
new file mode 100644
index 0000000..cdd8ad5
--- /dev/null
+++ b/include/btmtk_fw_log.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef __BTMTK_FW_LOG_H__
+#define __BTMTK_FW_LOG_H__
+
+#include "btmtk_main.h"
+#include "btmtk_chip_reset.h"
+
+#define BT_FWLOG_IOC_MAGIC (0xfc)
+#define BT_FWLOG_IOC_ON_OFF _IOW(BT_FWLOG_IOC_MAGIC, 0, int)
+#define BT_FWLOG_IOC_SET_LEVEL _IOW(BT_FWLOG_IOC_MAGIC, 1, int)
+#define BT_FWLOG_IOC_GET_LEVEL _IOW(BT_FWLOG_IOC_MAGIC, 2, int)
+#define BT_FWLOG_OFF 0x00
+#define BT_FWLOG_ON 0xFF
+
+#define DRV_RETURN_SPECIFIC_HCE_ONLY 1 /* Currently only allow 0xFC26 */
+#define KPI_WITHOUT_TYPE 0 /* bluetooth kpi */
+
+#ifdef STATIC_REGISTER_FWLOG_NODE
+#define FIXED_STPBT_MAJOR_DEV_ID 111
+#endif
+
+/* Device node */
+#if (USE_DEVICE_NODE == 0)
+#if CFG_SUPPORT_MULTI_DEV_NODE
+ #define BT_FWLOG_DEV_NODE "stpbt_multi_fwlog"
+#else
+ #define BT_FWLOG_DEV_NODE "stpbtfwlog"
+#endif
+#else
+#if CFG_SUPPORT_MULTI_DEV_NODE
+ #define BT_FWLOG_DEV_NODE "multi_fw_log_bt"
+#else
+ #define BT_FWLOG_DEV_NODE "fw_log_bt"
+#endif
+#endif
+
+#define PROC_ROOT_DIR "stpbt"
+#define PROC_BT_CHIP_RESET_COUNT "bt_chip_reset_count"
+
+struct btmtk_fops_fwlog {
+ dev_t g_devIDfwlog;
+ struct cdev BT_cdevfwlog;
+ wait_queue_head_t fw_log_inq;
+ struct sk_buff_head fwlog_queue;
+ struct class *pBTClass;
+ struct device *pBTDevfwlog;
+ spinlock_t fwlog_lock;
+ u8 btmtk_bluetooth_kpi;
+ struct sk_buff_head usr_opcode_queue;
+};
+
+void btmtk_init_node(void);
+void btmtk_deinit_node(void);
+void fw_log_bt_event_cb(void);
+void fw_log_bt_state_cb(uint8_t state);
+/** file_operations: stpbtfwlog */
+int btmtk_fops_openfwlog(struct inode *inode, struct file *file);
+int btmtk_fops_closefwlog(struct inode *inode, struct file *file);
+ssize_t btmtk_fops_readfwlog(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
+ssize_t btmtk_fops_writefwlog(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
+unsigned int btmtk_fops_pollfwlog(struct file *filp, poll_table *wait);
+long btmtk_fops_unlocked_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg);
+long btmtk_fops_compat_ioctlfwlog(struct file *filp, unsigned int cmd, unsigned long arg);
+int btmtk_dispatch_fwlog(struct btmtk_dev *bdev, struct sk_buff *skb);
+int btmtk_dispatch_fwlog_bluetooth_kpi(struct btmtk_dev *bdev, u8 *buf, int len, u8 type);
+void btmtk_fwdump_wake_unlock(void);
+
+
+#endif /* __BTMTK_FW_LOG_H__ */
diff --git a/include/btmtk_main.h b/include/btmtk_main.h
new file mode 100644
index 0000000..29523a5
--- /dev/null
+++ b/include/btmtk_main.h
@@ -0,0 +1,907 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef __BTMTK_MAIN_H__
+#define __BTMTK_MAIN_H__
+#include "btmtk_define.h"
+
+#define DEFAULT_COUNTRY_TABLE_NAME "btPowerTable.dat"
+
+#ifdef CHIP_IF_USB
+#define DEFAULT_DEBUG_SOP_NAME "usb_debug"
+#elif defined(CHIP_IF_SDIO)
+#define DEFAULT_DEBUG_SOP_NAME "sdio_debug"
+#elif defined(CHIP_IF_UART_TTY)
+#define DEFAULT_DEBUG_SOP_NAME "uart_debug"
+#endif
+
+//static inline struct sk_buff *mtk_add_stp(struct btmtk_dev *bdev, struct sk_buff *skb);
+
+#define hci_dev_test_and_clear_flag(hdev, nr) test_and_clear_bit((nr), (hdev)->dev_flags)
+
+/* h4_recv */
+#define hci_skb_pkt_type(skb) bt_cb((skb))->pkt_type
+#define hci_skb_expect(skb) bt_cb((skb))->expect
+#define hci_skb_opcode(skb) bt_cb((skb))->hci.opcode
+
+/* HCI bus types */
+#define HCI_VIRTUAL 0
+#define HCI_USB 1
+#define HCI_PCCARD 2
+#define HCI_UART 3
+#define HCI_RS232 4
+#define HCI_PCI 5
+#define HCI_SDIO 6
+#define HCI_SPI 7
+#define HCI_I2C 8
+#define HCI_SMD 9
+
+#define HCI_TYPE_SIZE 1
+
+/* this for 7663 need download patch staus
+ * 0:
+ * patch download is not complete/BT get patch semaphore fail (WiFi get semaphore success)
+ * 1:
+ * patch download is complete
+ * 2:
+ * patch download is not complete/BT get patch semaphore success
+ */
+#define MT766X_PATCH_IS_DOWNLOAD_BY_OTHER 0
+#define MT766X_PATCH_READY 1
+#define MT766X_PATCH_NEED_DOWNLOAD 2
+
+/* this for 79XX need download patch staus
+ * 0:
+ * patch download is not complete, BT driver need to download patch
+ * 1:
+ * patch is downloading by Wifi,BT driver need to retry until status = PATCH_READY
+ * 2:
+ * patch download is complete, BT driver no need to download patch
+ */
+#define PATCH_ERR -1
+#define PATCH_NEED_DOWNLOAD 0
+#define PATCH_IS_DOWNLOAD_BY_OTHER 1
+#define PATCH_READY 2
+
+/* 0:
+ * using legacy wmt cmd/evt to download fw patch, usb/sdio just support 0 now
+ * 1:
+ * using DMA to download fw patch
+ */
+#define PATCH_DOWNLOAD_USING_WMT 0
+#define PATCH_DOWNLOAD_USING_DMA 1
+
+#define PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME 1
+#define PATCH_DOWNLOAD_PHASE1_2_RETRY 5
+#define PATCH_DOWNLOAD_PHASE3_DELAY_TIME 20
+#define PATCH_DOWNLOAD_PHASE3_RETRY 20
+#define PATCH_DOWNLOAD_PHASE3_SECURE_BOOT_DELAY_TIME 200
+#define TIME_MULTIPL 1000
+#define TIME_US_OFFSET_RANGE 2000
+
+/* * delay and retrey for main_send_cmd */
+#define WMT_DELAY_TIMES 100
+#define DELAY_TIMES 20
+#define RETRY_TIMES 20
+
+/* Expected minimum supported interface */
+#define BT_MCU_MINIMUM_INTERFACE_NUM 4
+
+/* Bus event */
+#define HIF_EVENT_PROBE 0
+#define HIF_EVENT_DISCONNECT 1
+#define HIF_EVENT_SUSPEND 2
+#define HIF_EVENT_RESUME 3
+#define HIF_EVENT_STANDBY 4
+#define HIF_EVENT_SUBSYS_RESET 5
+#define HIF_EVENT_WHOLE_CHIP_RESET 6
+#define HIF_EVENT_FW_DUMP 7
+
+
+#define CHAR2HEX_SIZE 4
+
+/**
+ * For chip reset pin
+ */
+#define RESET_PIN_SET_LOW_TIME 100
+
+/* stpbtfwlog setting */
+#define FWLOG_QUEUE_COUNT (400 * BT_MCU_MINIMUM_INTERFACE_NUM)
+#define FWLOG_ASSERT_QUEUE_COUNT 45000
+#define FWLOG_BLUETOOTH_KPI_QUEUE_COUNT 400
+#define HCI_MAX_COMMAND_SIZE 255
+#define HCI_MAX_COMMAND_BUF_SIZE (HCI_MAX_COMMAND_SIZE * 3)
+#ifndef HCI_MAX_ISO_SIZE
+#define HCI_MAX_ISO_SIZE 340
+#endif
+
+/* fwlog information define */
+#define FWLOG_TYPE 0xF0
+#define FWLOG_LEN_SIZE 2
+#define FWLOG_TL_SIZE (HCI_TYPE_SIZE + FWLOG_LEN_SIZE)
+#define FWLOG_ATTR_TYPE_LEN 1
+#define FWLOG_ATTR_LEN_LEN 1
+#define FWLOG_ATTR_RX_LEN_LEN 2
+#define FWLOG_ATTR_TL_SIZE (FWLOG_ATTR_TYPE_LEN + FWLOG_ATTR_LEN_LEN)
+
+#define FWLOG_HCI_IDX 0x00
+#define FWLOG_DONGLE_IDX 0x01
+#define FWLOG_TX 0x10
+#define FWLOG_RX 0x11
+
+/* total fwlog info len */
+#define FWLOG_PRSV_LEN 32
+
+#define COUNTRY_CODE_LEN 2
+
+
+#define EDR_MIN -32
+#define EDR_MAX 20
+#define EDR_MIN_LV9 13
+#define BLE_MIN -29
+#define BLE_MAX 20
+#define EDR_MIN_R1 -64
+#define EDR_MAX_R1 40
+#define EDR_MIN_LV9_R1 26
+#define BLE_MIN_R1 -58
+#define BLE_MAX_R1 40
+#define EDR_MIN_R2 -128
+#define EDR_MAX_R2 80
+#define EDR_MIN_LV9_R2 52
+#define BLE_MIN_R2 -116
+#define BLE_MAX_R2 80
+
+#define ERR_PWR -9999
+
+#define WAIT_POWERKEY_TIMEOUT 5000
+#define WAIT_FW_DUMP_TIMEOUT 15000
+#define WAIT_DRV_OWN_TIMEOUT 3000
+
+#define SEPARATOR_LEN 2
+#define STP_CRC_LEN 2
+#define TEMP_LEN 260
+#define SEARCH_LEN 32
+#define TEXT_LEN 128
+
+#define DUAL_BT_FLAG (0x1 << 5)
+
+/* CMD&Event sent by driver */
+#define READ_EFUSE_CMD_LEN 18
+#define READ_EFUSE_EVT_HDR_LEN 9
+#define READ_EFUSE_CMD_BLOCK_OFFSET 10
+
+#define CHECK_LD_PATCH_CMD_LEN 9
+#define CHECK_LD_PATCH_EVT_HDR_LEN 7
+#define CHECK_LD_PATCH_EVT_RESULT_OFFSET 6 /* need confirm later */
+
+#define HWERR_EVT_LEN 4
+
+#define LD_PATCH_EVT_LEN 8
+
+#define HCI_RESET_CMD_LEN 4
+#define HCI_RESET_EVT_LEN 7
+
+#define WMT_RESET_CMD_LEN 9
+#define WMT_RESET_EVT_LEN 8
+
+#define WMT_POWER_ON_CMD_LEN 10
+#define WMT_POWER_ON_EVT_HDR_LEN 7
+#define WMT_POWER_ON_EVT_RESULT_OFFSET 7
+
+#define WMT_POWER_OFF_CMD_LEN 10
+#define WMT_POWER_OFF_EVT_HDR_LEN 7
+#define WMT_POWER_OFF_EVT_RESULT_OFFSET 7
+
+#define PICUS_ENABLE_CMD_LEN 8
+#define PICUS_ENABLE_EVT_HDR_LEN 9
+
+#define PICUS_DISABLE_CMD_LEN 8
+#define PICUS_DISABLE_EVT_HDR_LEN 9
+
+#define RES_APCF_CMD_LEN 9
+#define RES_APCF_EVT_LEN 5
+
+#define READ_ADDRESS_CMD_LEN 4
+#define READ_ADDRESS_EVT_HDR_LEN 7
+
+#define WOBLE_ENABLE_DEFAULT_CMD_LEN 40
+#define WOBLE_ENABLE_DEFAULT_EVT_LEN 5
+
+#define WOBLE_DISABLE_DEFAULT_CMD_LEN 9
+#define WOBLE_DISABLE_DEFAULT_EVT_LEN 5
+
+#define RADIO_OFF_CMD_LEN 9
+#define RADIO_OFF_EVT_LEN 5
+
+#define RADIO_ON_CMD_LEN 9
+#define RADIO_ON_EVT_LEN 5
+
+#define APCF_FILTER_CMD_LEN 14
+#define APCF_FILTER_EVT_HDR_LEN 8
+
+#define APCF_CMD_LEN 43
+#define APCF_EVT_HDR_LEN 7
+
+#define APCF_DELETE_CMD_LEN 7
+#define APCF_DELETE_EVT_HDR_LEN 8
+
+#define APCF_RESUME_EVT_HDR_LEN 7
+
+#define CHECK_WOBX_DEBUG_CMD_LEN 8
+#define CHECK_WOBX_DEBUG_EVT_HDR_LEN 2
+
+#define SET_STP_CMD_LEN 13
+#define SET_STP_EVT_LEN 9
+
+#define SET_STP1_CMD_LEN 16
+#define SET_STP1_EVT_LEN 19
+
+#define SET_SLEEP_CMD_LEN 11
+#define SET_SLEEP_EVT_LEN 7
+
+#define EVT_HDR_LEN 2
+
+#if (USE_DEVICE_NODE == 0)
+#define ASSERT_CMD_LEN 9
+#else
+#define ASSERT_CMD_LEN 4
+#endif
+#define TXPOWER_CMD_LEN 16
+#define TXPOWER_EVT_LEN 7
+
+#define FW_COREDUMP_CMD_LEN 4
+#define HCI_RESET_CMD_LEN 4
+#define READ_ISO_PACKET_SIZE_CMD_HDR_LEN 4
+
+#define AUDIO_SETTING_CMD_LEN 8
+#define AUDIO_SETTING_EVT_LEN 7
+
+#define READ_PINMUX_CMD_LEN 8
+#define READ_PINMUX_EVT_CMP_LEN 6
+#define READ_PINMUX_EVT_REAL_LEN 11
+
+#define WRITE_PINMUX_CMD_LEN 12
+#define WRITE_PINMUX_EVT_LEN 7
+
+#define PINMUX_REG_NUM 2
+
+#define WRITE_PINMUX_CMD_LEN_7902 7
+#define WRITE_PINMUX_EVT_LEN_7902 7
+
+#define PINMUX_REG_NUM_7902 4
+
+#define FW_VERSION_BUF_SIZE 256
+#define FW_VERSION_KEY_WORDS "t-neptune"
+
+#if BUILD_QA_DBG
+#define CFG_SHOW_FULL_MACADDR 1
+#else
+#define CFG_SHOW_FULL_MACADDR 0
+#endif
+
+#if CFG_SHOW_FULL_MACADDR
+#define MACSTR "%02X:%02X:%02X:%02X:%02X:%02X"
+#define MAC2STR(a) ((unsigned char *)a)[0], ((unsigned char *)a)[1], ((unsigned char *)a)[2],\
+ ((unsigned char *)a)[3], ((unsigned char *)a)[4], ((unsigned char *)a)[5]
+#else
+#define MACSTR "%02X:%02X:**:**:**:%02X"
+#define MAC2STR(a) ((unsigned char *)a)[0], ((unsigned char *)a)[1], ((unsigned char *)a)[5]
+#endif
+
+enum {
+ RES_1 = 0,
+ RES_DOT_5,
+ RES_DOT_25
+};
+
+enum {
+ CHECK_SINGLE_SKU_PWR_MODE = 0,
+ CHECK_SINGLE_SKU_EDR_MAX,
+ CHECK_SINGLE_SKU_BLE,
+ CHECK_SINGLE_SKU_BLE_2M,
+ CHECK_SINGLE_SKU_BLE_LR_S2,
+ CHECK_SINGLE_SKU_BLE_LR_S8,
+ CHECK_SINGLE_SKU_ALL
+};
+
+enum {
+ DISABLE_LV9 = 0,
+ ENABLE_LV9
+};
+
+enum {
+ DIFF_MODE_3DB = 0,
+ DIFF_MODE_0DB
+};
+
+struct btmtk_cif_state {
+ unsigned char ops_enter;
+ unsigned char ops_end;
+ unsigned char ops_error;
+};
+
+enum TX_TYPE {
+ BTMTK_TX_CMD_FROM_DRV = 0, /* send hci cmd and wmt cmd by driver */
+ BTMTK_TX_ACL_FROM_DRV, /* send acl pkt with load rompatch by driver */
+ BTMTK_TX_PKT_FROM_HOST, /* send pkt from host, include acl and hci */
+ BTMTK_TX_PKT_SEND_DIRECT,/* send tx not through tx_thread */
+};
+
+enum bt_state {
+ FUNC_OFF = 0,
+ TURNING_ON = 1,
+ PRE_ON_AFTER_CAL = 2,
+ FUNC_ON = 3,
+ RESET_START = 4,
+ RESET_END = 5
+};
+
+struct bt_power_setting {
+ int8_t EDR_Max;
+ int8_t LV9;
+ int8_t DM;
+ int8_t IR;
+ int8_t BLE_1M;
+ int8_t BLE_2M;
+ int8_t BLE_LR_S2;
+ int8_t BLE_LR_S8;
+ char country_code[COUNTRY_CODE_LEN + 1];
+};
+
+enum {
+ BTMTK_DONGLE_STATE_UNKNOWN,
+ BTMTK_DONGLE_STATE_POWER_ON,
+ BTMTK_DONGLE_STATE_POWER_OFF,
+ BTMTK_DONGLE_STATE_ERROR,
+};
+
+enum {
+ HW_ERR_NONE = 0x00,
+ HW_ERR_CODE_CHIP_RESET = 0xF0,
+ HW_ERR_CODE_USB_DISC = 0xF1,
+ HW_ERR_CODE_CORE_DUMP = 0xF2,
+ HW_ERR_CODE_POWER_ON = 0xF3,
+ HW_ERR_CODE_POWER_OFF = 0xF4,
+ HW_ERR_CODE_SET_SLEEP_CMD = 0xF5,
+ HW_ERR_CODE_RESET_STACK_AFTER_WOBLE = 0xF6,
+};
+
+/* Please keep sync with btmtk_set_state function */
+enum {
+ /* BTMTK_STATE_UNKNOWN = 0, */
+ BTMTK_STATE_INIT = 1,
+ BTMTK_STATE_DISCONNECT,
+ BTMTK_STATE_PROBE,
+ BTMTK_STATE_WORKING,
+ BTMTK_STATE_SUSPEND,
+ BTMTK_STATE_RESUME,
+ BTMTK_STATE_FW_DUMP,
+ BTMTK_STATE_STANDBY,
+ BTMTK_STATE_SUBSYS_RESET,
+ BTMTK_STATE_SEND_ASSERT,
+ BTMTK_STATE_CLOSED,
+ BTMTK_STATE_ERR,
+
+ BTMTK_STATE_MSG_NUM
+};
+
+/* Please keep sync with btmtk_fops_set_state function */
+enum {
+ /* BTMTK_FOPS_STATE_UNKNOWN = 0, */
+ BTMTK_FOPS_STATE_INIT = 1,
+ BTMTK_FOPS_STATE_OPENING, /* during opening */
+ BTMTK_FOPS_STATE_OPENED, /* open in fops_open */
+ BTMTK_FOPS_STATE_CLOSING, /* during closing */
+ BTMTK_FOPS_STATE_CLOSED, /* closed */
+
+ BTMTK_FOPS_STATE_MSG_NUM
+};
+
+enum {
+ BTMTK_EVENT_COMPARE_STATE_UNKNOWN,
+ BTMTK_EVENT_COMPARE_STATE_NOTHING_NEED_COMPARE,
+ BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE,
+ BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS,
+};
+
+enum {
+ HCI_SNOOP_TYPE_CMD_STACK = 0,
+ HCI_SNOOP_TYPE_CMD_HIF,
+ HCI_SNOOP_TYPE_EVT_STACK,
+ HCI_SNOOP_TYPE_EVT_HIF,
+ HCI_SNOOP_TYPE_ADV_EVT_STACK,
+ HCI_SNOOP_TYPE_ADV_EVT_HIF,
+ HCI_SNOOP_TYPE_NOCP_EVT_STACK,
+ HCI_SNOOP_TYPE_NOCP_EVT_HIF,
+ HCI_SNOOP_TYPE_TX_ACL_STACK,
+ HCI_SNOOP_TYPE_TX_ACL_HIF,
+ HCI_SNOOP_TYPE_RX_ACL_STACK,
+ HCI_SNOOP_TYPE_RX_ACL_HIF,
+ HCI_SNOOP_TYPE_TX_ISO_STACK,
+ HCI_SNOOP_TYPE_TX_ISO_HIF,
+ HCI_SNOOP_TYPE_RX_ISO_STACK,
+ HCI_SNOOP_TYPE_RX_ISO_HIF,
+ HCI_SNOOP_TYPE_MAX
+};
+
+enum {
+ DEBUG_SOP_SLEEP,
+ DEBUG_SOP_WAKEUP,
+ DEBUG_SOP_NO_RESPONSE,
+
+ DEBUG_SOP_NONE
+};
+
+struct dump_debug_cr {
+ u32 addr_w;
+ u32 value_w;
+ u32 addr_r;
+};
+
+struct h4_recv_pkt {
+ u8 type; /* Packet type */
+ u8 hlen; /* Header length (not include pkt type)*/
+ u8 loff; /* Data length offset in header (not include pkt type)*/
+ u8 lsize; /* Data length field size */
+ u16 maxlen; /* Max overall packet length */
+ int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
+};
+
+#pragma pack(1)
+struct _PATCH_HEADER {
+ u8 ucDateTime[16];
+ u8 ucPlatform[4];
+ u16 u2HwVer;
+ u16 u2SwVer;
+ u32 u4MagicNum;
+};
+
+struct _Global_Descr {
+ u32 u4PatchVer;
+ u32 u4SubSys;
+ u32 u4FeatureOpt;
+ u32 u4SectionNum;
+};
+
+struct _Section_Map {
+ u32 u4SecType;
+ u32 u4SecOffset;
+ u32 u4SecSize;
+ union {
+ u32 u4SecSpec[SECTION_SPEC_NUM];
+ struct {
+ u32 u4DLAddr;
+ u32 u4DLSize;
+ u32 u4SecKeyIdx;
+ u32 u4AlignLen;
+ u32 u4SecType;
+ u32 u4DLModeCrcType;
+ u32 u4Crc;
+ u32 reserved[6];
+ } bin_info_spec;
+ };
+};
+#pragma pack()
+
+#if (USE_DEVICE_NODE == 1)
+#define BT_HCI_MAX_FRAME_SIZE 4096
+
+
+#define RHW_WRITE_TYPE 0x40
+#define RHW_READ_TYPE 0x41
+
+#define RHW_VAL_LEN 4
+#define RHW_ADDR_LEN 4
+#define RHW_PKT_HDR_LEN 4
+#define RHW_PKT_LEN 13
+#define RHW_PKT_COMP_LEN 9
+#define RHW_ADDR_OFFSET_CMD 5
+#define RHW_VAL_OFFSET_CMD 9
+
+#define H4_RECV_RHW_WRITE \
+ .type = RHW_WRITE_TYPE, \
+ .hlen = RHW_PKT_HDR_LEN, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = RHW_PKT_LEN
+
+#define H4_RECV_RHW_READ \
+ .type = RHW_READ_TYPE, \
+ .hlen = RHW_PKT_HDR_LEN, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = RHW_PKT_LEN
+
+#define H4_RECV_ACL \
+ .type = HCI_ACLDATA_PKT, \
+ .hlen = HCI_ACL_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = BT_HCI_MAX_FRAME_SIZE
+
+#else // (USE_DEVICE_NODE == 0)
+#define H4_RECV_ACL \
+ .type = HCI_ACLDATA_PKT, \
+ .hlen = HCI_ACL_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE
+#endif
+
+#define H4_RECV_SCO \
+ .type = HCI_SCODATA_PKT, \
+ .hlen = HCI_SCO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_SCO_SIZE
+
+#define H4_RECV_EVENT \
+ .type = HCI_EVENT_PKT, \
+ .hlen = HCI_EVENT_HDR_SIZE, \
+ .loff = 1, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_EVENT_SIZE
+
+/* yumin todo */
+// TODO: replace by kernel constant if kernel support new spec
+#define HCI_ISODATA_PKT 0x05
+#define HCI_ISO_HDR_SIZE 4
+#define H4_RECV_ISO \
+ .type = HCI_ISODATA_PKT, \
+ .hlen = HCI_ISO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE
+
+struct btmtk_dev {
+ struct hci_dev *hdev;
+ unsigned long hdev_flags;
+ unsigned long flags;
+ void *intf_dev;
+ void *cif_dev;
+
+ struct work_struct work;
+ struct work_struct waker;
+ struct work_struct reset_waker;
+
+ struct timer_list chip_reset_timer;
+
+ int recv_evt_len;
+ int tx_in_flight;
+ spinlock_t txlock;
+ spinlock_t rxlock;
+
+ struct sk_buff *evt_skb;
+ struct sk_buff *sco_skb;
+
+ /* For ble iso packet size */
+ int iso_threshold;
+
+ unsigned int sco_num;
+ int isoc_altsetting;
+
+ int suspend_count;
+
+ /* For tx queue */
+ unsigned long tx_state;
+
+ /* For rx queue */
+ struct workqueue_struct *workqueue;
+ struct sk_buff_head rx_q;
+ struct work_struct rx_work;
+ struct sk_buff *rx_skb;
+
+ wait_queue_head_t p_wait_event_q;
+
+ unsigned int subsys_reset;
+ unsigned int chip_reset;
+ unsigned char *rom_patch_bin_file_name;
+ unsigned int chip_id;
+ unsigned int flavor;
+ unsigned int dualBT;
+ unsigned int fw_version;
+ unsigned char dongle_index;
+ unsigned char power_state;
+ unsigned char fops_state;
+ unsigned char interface_state;
+ struct btmtk_cif_state *cif_state;
+
+ /* io buffer for usb control transfer */
+ unsigned char *io_buf;
+
+ unsigned char *setting_file;
+ unsigned char bdaddr[BD_ADDRESS_SIZE];
+
+ unsigned char *bt_cfg_file_name;
+ struct bt_cfg_struct bt_cfg;
+
+ /* single sku */
+ unsigned char *country_file_name;
+
+ int get_hci_reset;
+
+ /* debug sop */
+ struct debug_reg_struct debug_sop_reg_dump;
+ unsigned char debug_sop_file_name[MAX_BIN_FILE_NAME_LEN];
+
+ struct _Section_Map *sectionMap_table;
+
+ /* dynamic fw download */
+ struct work_struct dynamic_fwdl_work;
+ unsigned int fw_bin_info;
+
+#if (USE_DEVICE_NODE == 1)
+ /* asynchronize tx/rx */
+ struct work_struct async_trx_work;
+#endif
+ /* completion */
+ struct completion dump_comp;
+ struct completion drv_own_comp;
+};
+
+#if (USE_DEVICE_NODE == 1)
+typedef void (*cif_chrdev_fw_log_state_ptr)(uint8_t state);
+#endif
+
+typedef int (*cif_bt_init_ptr)(void);
+typedef void (*cif_bt_exit_ptr)(void);
+typedef int (*cif_pre_open_ptr)(struct btmtk_dev *bdev);
+typedef int (*cif_open_ptr)(struct hci_dev *hdev);
+typedef void (*cif_open_done_ptr)(struct btmtk_dev *bdev);
+typedef int (*cif_close_ptr)(struct hci_dev *hdev);
+typedef int (*cif_reg_read_ptr)(struct btmtk_dev *bdev, u32 reg, u32 *val);
+typedef int (*cif_reg_write_ptr)(struct btmtk_dev *bdev, u32 reg, u32 val);
+typedef int (*cif_send_cmd_ptr)(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type);
+typedef int (*cif_send_and_recv_ptr)(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type);
+typedef int (*cif_event_filter_ptr)(struct btmtk_dev *bdev, struct sk_buff *skb);
+typedef int (*cif_subsys_reset_ptr)(struct btmtk_dev *bdev);
+typedef int (*cif_whole_reset_ptr)(struct btmtk_dev *bdev);
+typedef void (*cif_chip_reset_notify_ptr)(struct btmtk_dev *bdev);
+typedef void (*cif_mutex_lock_ptr)(struct btmtk_dev *bdev);
+typedef void (*cif_mutex_unlock_ptr)(struct btmtk_dev *bdev);
+typedef int (*cif_flush_ptr)(struct btmtk_dev *bdev);
+typedef void (*cif_log_init_ptr)(void (*log_event_cb)(void));
+typedef void (*cif_log_register_cb_ptr)(void (*func)(void));
+typedef ssize_t (*cif_log_read_to_user_ptr)(char __user *buf, size_t count);
+typedef unsigned int (*cif_log_get_buf_size_ptr)(void);
+typedef void (*cif_log_deinit_ptr)(void);
+typedef int (*cif_log_handler_ptr)(u8 *buf, u32 size);
+typedef int (*cif_dl_dma_ptr)(struct btmtk_dev *bdev, u8 *image,
+ u8 *fwbuf, int section_dl_size, int section_offset);
+typedef void (*cif_dump_debug_sop_ptr)(struct btmtk_dev *bdev);
+typedef void (*cif_waker_notify_ptr)(struct btmtk_dev *bdev);
+typedef int (*cif_enter_standby_ptr)(void);
+typedef int (*cif_set_para_ptr)(struct btmtk_dev *bdev, int val);
+
+struct hif_hook_ptr {
+#if (USE_DEVICE_NODE == 1)
+ cif_chrdev_fw_log_state_ptr fw_log_state;
+#endif
+ cif_bt_init_ptr init;
+ cif_bt_exit_ptr exit;
+ cif_pre_open_ptr pre_open;
+ cif_open_ptr open;
+ cif_open_done_ptr open_done;
+ cif_close_ptr close;
+ cif_reg_read_ptr reg_read;
+ cif_reg_write_ptr reg_write;
+ cif_send_cmd_ptr send_cmd;
+ cif_send_and_recv_ptr send_and_recv;
+ cif_event_filter_ptr event_filter;
+ cif_subsys_reset_ptr subsys_reset;
+ cif_whole_reset_ptr whole_reset;
+ cif_chip_reset_notify_ptr chip_reset_notify;
+ cif_mutex_lock_ptr cif_mutex_lock;
+ cif_mutex_unlock_ptr cif_mutex_unlock;
+ cif_flush_ptr flush;
+ cif_log_init_ptr log_init;
+ cif_log_register_cb_ptr log_register_cb;
+ cif_log_read_to_user_ptr log_read_to_user;
+ cif_log_get_buf_size_ptr log_get_buf_size;
+ cif_log_deinit_ptr log_deinit;
+ cif_log_handler_ptr log_handler;
+ cif_dl_dma_ptr dl_dma;
+ cif_dump_debug_sop_ptr dump_debug_sop;
+ cif_waker_notify_ptr waker_notify;
+ cif_enter_standby_ptr enter_standby;
+ cif_set_para_ptr set_para;
+ void *coredump_handler;
+};
+
+struct hci_snoop {
+ u8 buf[HCI_SNOOP_ENTRY_NUM][HCI_SNOOP_MAX_BUF_SIZE];
+ u8 len[HCI_SNOOP_ENTRY_NUM];
+ u16 actual_len[HCI_SNOOP_ENTRY_NUM];
+ char timestamp[HCI_SNOOP_ENTRY_NUM][HCI_SNOOP_TS_STR_LEN];
+ u8 index;
+};
+
+struct btmtk_main_info {
+ int chip_reset_flag;
+ atomic_t subsys_reset;
+ atomic_t chip_reset;
+ atomic_t subsys_reset_count;
+ atomic_t whole_reset_count;
+ atomic_t subsys_reset_conti_count;
+
+ u8 reset_stack_flag;
+ struct wakeup_source *fwdump_ws;
+ struct wakeup_source *woble_ws;
+ struct wakeup_source *eint_ws;
+#if WAKEUP_BT_IRQ
+ struct wakeup_source *irq_ws;
+#endif
+ struct hif_hook_ptr hif_hook;
+ struct bt_power_setting PWS;
+ /* save Hci Snoop for debug*/
+ struct hci_snoop snoop[HCI_SNOOP_TYPE_MAX];
+
+ u8 wmt_over_hci_header[WMT_OVER_HCI_HEADER_SIZE];
+ u8 read_iso_packet_size_cmd[READ_ISO_PACKET_CMD_SIZE];
+
+ /* record firmware version */
+ struct proc_dir_entry *proc_dir;
+ char fw_version_str[FW_VERSION_BUF_SIZE];
+
+ atomic_t fwlog_ref_cnt;
+};
+
+static inline int is_mt6639(u32 chip_id)
+{
+ chip_id &= 0xFFFF;
+ if (chip_id == 0x6639)
+ return 1;
+ return 0;
+}
+
+static inline int is_mt7902(u32 chip_id)
+{
+ chip_id &= 0xFFFF;
+ if (chip_id == 0x7902)
+ return 1;
+ return 0;
+}
+
+static inline int is_mt7922(u32 chip_id)
+{
+ chip_id &= 0xFFFF;
+ if (chip_id == 0x7922)
+ return 1;
+ return 0;
+}
+
+static inline int is_mt7961(u32 chip_id)
+{
+ chip_id &= 0xFFFF;
+ if (chip_id == 0x7961)
+ return 1;
+ return 0;
+}
+
+static inline int is_mt66xx(u32 chip_id)
+{
+ chip_id &= 0xFFFF;
+ if (chip_id == 0x6631 || chip_id == 0x6635)
+ return 1;
+ return 0;
+}
+
+/* Get BT whole packet length except hci type */
+static inline unsigned int get_pkt_len(unsigned char type, unsigned char *buf)
+{
+ unsigned int len = 0;
+
+ switch (type) {
+ /* Please reference hci header format
+ * AA = len
+ * xx = buf[0]
+ * cmd : 01 xx yy AA + payload
+ * acl : 02 xx yy AA AA + payload
+ * sco : 03 xx yy AA + payload
+ * evt : 04 xx AA + payload
+ * ISO : 05 xx yy AA AA + payload
+ */
+ case HCI_COMMAND_PKT:
+ len = buf[2] + 3;
+ break;
+ case HCI_ACLDATA_PKT:
+ len = buf[2] + ((buf[3] << 8) & 0xff00) + 4;
+ break;
+ case HCI_SCODATA_PKT:
+ len = buf[2] + 3;
+ break;
+ case HCI_EVENT_PKT:
+ len = buf[1] + 2;
+ break;
+ case HCI_ISO_PKT:
+ len = buf[2] + (((buf[3] & 0x3F) << 8) & 0xff00) + HCI_ISO_PKT_HEADER_SIZE;
+ break;
+ default:
+ len = 0;
+ }
+
+ return len;
+}
+
+unsigned char btmtk_get_chip_state(struct btmtk_dev *bdev);
+void btmtk_set_chip_state(struct btmtk_dev *bdev, unsigned char new_state);
+int btmtk_allocate_hci_device(struct btmtk_dev *bdev, int hci_bus_type);
+void btmtk_free_hci_device(struct btmtk_dev *bdev, int hci_bus_type);
+int btmtk_register_hci_device(struct btmtk_dev *bdev);
+int btmtk_deregister_hci_device(struct btmtk_dev *bdev);
+int btmtk_recv(struct hci_dev *hdev, const u8 *data, size_t count);
+int btmtk_recv_event(struct hci_dev *hdev, struct sk_buff *skb);
+int btmtk_recv_acl(struct hci_dev *hdev, struct sk_buff *skb);
+int btmtk_recv_iso(struct hci_dev *hdev, struct sk_buff *skb);
+#if (USE_DEVICE_NODE == 1)
+int btmtk_recv_rhw(struct hci_dev *hdev, struct sk_buff *skb);
+#endif
+int btmtk_send_init_cmds(struct btmtk_dev *hdev);
+int btmtk_send_deinit_cmds(struct btmtk_dev *hdev);
+int btmtk_load_rom_patch(struct btmtk_dev *bdev);
+struct btmtk_dev *btmtk_get_dev(void);
+int btmtk_cap_init(struct btmtk_dev *bdev);
+struct btmtk_main_info *btmtk_get_main_info(void);
+int btmtk_get_interface_num(void);
+int btmtk_reset_power_on(struct btmtk_dev *bdev);
+
+void btmtk_send_hw_err_to_host(struct btmtk_dev *bdev);
+void btmtk_free_setting_file(struct btmtk_dev *bdev);
+
+unsigned char btmtk_fops_get_state(struct btmtk_dev *bdev);
+
+void btmtk_hci_snoop_save(unsigned int type, u8 *buf, u32 len);
+void btmtk_hci_snoop_print(const u8 *buf, u32 len);
+void btmtk_hci_snoop_print_to_log(void);
+void *btmtk_kallsyms_lookup_name(const char *name);
+void btmtk_get_UTC_time_str(char *ts_str);
+void btmtk_reg_hif_hook(struct hif_hook_ptr *hook);
+int btmtk_main_cif_initialize(struct btmtk_dev *bdev, int hci_bus);
+void btmtk_main_cif_uninitialize(struct btmtk_dev *bdev, int hci_bus);
+int btmtk_main_cif_disconnect_notify(struct btmtk_dev *bdev, int hci_bus);
+int btmtk_load_code_from_bin(u8 **image, char *bin_name,
+ struct device *dev, u32 *code_len, u8 retry);
+int btmtk_main_send_cmd(struct btmtk_dev *bdev, const uint8_t *cmd,
+ const int cmd_len, const uint8_t *event, const int event_len, int delay,
+ int retry, int pkt_type);
+int btmtk_load_code_from_setting_files(char *setting_file_name,
+ struct device *dev, u32 *code_len, struct btmtk_dev *bdev);
+int btmtk_load_fw_cfg_setting(char *block_name, struct fw_cfg_struct *save_content,
+ int counter, u8 *searchcontent, enum fw_cfg_index_len index_length);
+int btmtk_send_assert_cmd(struct btmtk_dev *bdev);
+void btmtk_free_fw_cfg_struct(struct fw_cfg_struct *fw_cfg, int count);
+struct btmtk_dev **btmtk_get_pp_bdev(void);
+void btmtk_load_debug_sop_register(char *debug_sop_name, struct device *dev, struct btmtk_dev *bdev);
+void btmtk_clean_debug_reg_file(struct btmtk_dev *bdev);
+int btmtk_dynamic_load_rom_patch(struct btmtk_dev *bdev, u32 binInfo);
+
+int32_t btmtk_set_sleep(struct hci_dev *hdev, u_int8_t need_wait);
+int32_t bgfsys_bt_patch_dl(void);
+int btmtk_efuse_read(struct btmtk_dev *bdev, u16 addr, u8 *value);
+void btmtk_set_country_code_from_wifi(char *code);
+#if (USE_DEVICE_NODE == 1)
+int rx_skb_enqueue(struct sk_buff *skb);
+int btmtk_chardev_init(void);
+void btmtk_connsys_log_init(void (*log_event_cb)(void));
+void btmtk_connsys_log_deinit(void);
+int btmtk_connsys_log_handler(u8 *buf, u32 size);
+ssize_t btmtk_connsys_log_read_to_user(char __user *buf, size_t count);
+unsigned int btmtk_connsys_log_get_buf_size(void);
+int32_t btmtk_intcmd_set_fw_log(uint8_t flag);
+int btmtk_send_connfem_cmd(struct btmtk_dev *bdev);
+#endif
+
+#endif /* __BTMTK_MAIN_H__ */
diff --git a/include/btmtk_queue.h b/include/btmtk_queue.h
new file mode 100644
index 0000000..c7bfe55
--- /dev/null
+++ b/include/btmtk_queue.h
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef __BTMTK_QUEUE_H__
+#define __BTMTK_QUEUE_H__
+
+#if (USE_DEVICE_NODE == 1)
+/*******************************************************************************
+* C O N S T A N T
+********************************************************************************
+*/
+#define RING_BUFFER_SIZE (32768)
+#define DRIVER_CMD_CHECK 0
+
+ /*******************************************************************************
+ * D A T A T Y P E S
+ ********************************************************************************
+ */
+typedef void (*BT_RX_EVENT_CB) (void);
+
+struct bt_ring_buffer_mgmt {
+ u8 buf[RING_BUFFER_SIZE];
+ u32 write_idx;
+ u32 read_idx;
+ spinlock_t lock;
+};
+
+s32 rx_skb_enqueue(struct sk_buff *skb);
+u8 btmtk_rx_data_valid(void);
+void btmtk_register_rx_event_cb(BT_RX_EVENT_CB cb);
+
+int32_t btmtk_receive_data(struct hci_dev *hdev, u8 *buf, u32 count);
+int32_t btmtk_send_data(struct hci_dev *hdev, u8 *buf, u32 count);
+
+#endif
+
+#endif /* __BTMTK_QUEUE_H__ */
diff --git a/include/btmtk_woble.h b/include/btmtk_woble.h
new file mode 100644
index 0000000..e2183aa
--- /dev/null
+++ b/include/btmtk_woble.h
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef __BTMTK_WOBLE_H__
+#define __BTMTK_WOBLE_H__
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+
+/* Define for WoBLE */
+#define WOBLE_SETTING_COUNT 10
+#define WOBLE_EVENT_INTERVAL_TIMO 500
+#define WOBLE_COMP_EVENT_TIMO 5000
+
+/* WOBX attribute type */
+#define WOBX_TRIGGER_INFO_ADDR_TYPE 1
+#define WOBX_TRIGGER_INFO_ADV_DATA_TYPE 2
+#define WOBX_TRIGGER_INFO_TRACE_LOG_TYPE 3
+#define WOBX_TRIGGER_INFO_SCAN_LOG_TYPE 4
+#define WOBX_TRIGGER_INFO_TRIGGER_CNT_TYPE 5
+
+struct btmtk_woble {
+ unsigned char *woble_setting_file_name;
+ unsigned int woble_setting_len;
+
+ struct fw_cfg_struct woble_setting_apcf[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac_location[WOBLE_SETTING_COUNT];
+
+ struct fw_cfg_struct woble_setting_radio_off;
+ struct fw_cfg_struct woble_setting_wakeup_type;
+ struct fw_cfg_struct woble_setting_radio_off_status_event;
+ /* complete event */
+ struct fw_cfg_struct woble_setting_radio_off_comp_event;
+
+ struct fw_cfg_struct woble_setting_radio_on;
+ struct fw_cfg_struct woble_setting_radio_on_status_event;
+ struct fw_cfg_struct woble_setting_radio_on_comp_event;
+
+ /* set apcf after resume(radio on) */
+ struct fw_cfg_struct woble_setting_apcf_resume[WOBLE_SETTING_COUNT];
+
+ /* Foe Woble eint */
+ unsigned int wobt_irq;
+ int wobt_irqlevel;
+ atomic_t irq_enable_count;
+ struct input_dev *WoBLEInputDev;
+ void *bdev;
+};
+
+int btmtk_woble_suspend(struct btmtk_woble *bt_woble);
+int btmtk_woble_resume(struct btmtk_woble *bt_woble);
+int btmtk_woble_initialize(struct btmtk_dev *bdev, struct btmtk_woble *bt_woble);
+void btmtk_woble_uninitialize(struct btmtk_woble *bt_woble);
+void btmtk_woble_wake_unlock(struct btmtk_dev *bdev);
+#if WAKEUP_BT_IRQ
+void btmtk_sdio_irq_wake_lock_timeout(struct btmtk_dev *bdev);
+#endif
+int btmtk_send_apcf_reserved(struct btmtk_dev *bdev);
+
+#endif /* __BTMTK_WOBLE_H__ */
diff --git a/include/sdio/btmtk_sdio.h b/include/sdio/btmtk_sdio.h
new file mode 100644
index 0000000..e360e83
--- /dev/null
+++ b/include/sdio/btmtk_sdio.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef _BTMTK_SDIO_H_
+#define _BTMTK_SDIO_H_
+/* It's for reset procedure */
+#include <linux/mmc/sdio_ids.h>
+#include <linux/module.h>
+
+#include <linux/of_gpio.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_woble.h"
+#include "btmtk_buffer_mode.h"
+#include "btmtk_chip_reset.h"
+
+#ifndef BTMTK_SDIO_DEBUG
+#define BTMTK_SDIO_DEBUG 0
+#endif
+
+/**
+ * Card-relate definition.
+ */
+#ifndef SDIO_VENDOR_ID_MEDIATEK
+#define SDIO_VENDOR_ID_MEDIATEK 0x037A
+#endif
+
+#define HCI_HEADER_LEN 4
+
+#define MTK_STP_TLR_SIZE 2
+#define STP_HEADER_LEN 4
+#define STP_HEADER_CRC_LEN 2
+#define HCI_MAX_COMMAND_SIZE 255
+#define URB_MAX_BUFFER_SIZE (4*1024)
+#define BTMTK_SDIO_FUNC 2
+
+/* common register address */
+#define CCIR 0x0000
+#define CHLPCR 0x0004
+#define CSDIOCSR 0x0008
+#define CHCR 0x000C
+#define CHISR 0x0010
+#define CHIER 0x0014
+#define CTDR 0x0018
+#define CRDR 0x001C
+#define CTFSR 0x0020
+#define CRPLR 0x0024
+#define CSICR 0x00C0
+#define PD2HRM0R 0x00DC
+#define SWPCDBGR 0x0154
+#define PH2DSM0R 0x00C4
+/* PH2DSM0R*/
+#define PH2DSM0R_DRIVER_OWN 0x00000001
+/* CHLPCR */
+#define C_FW_INT_EN_SET 0x00000001
+#define C_FW_INT_EN_CLEAR 0x00000002
+/* CHISR */
+#define RX_PKT_LEN 0xFFFF0000
+#define FIRMWARE_INT 0x0000FE00
+/* PD2HRM0R */
+#define PD2HRM0R_DRIVER_OWN 0x00000001
+#define PD2HRM0R_FW_OWN 0x00000000
+/* MCU notify host dirver for L0.5 reset */
+#define FIRMWARE_INT_BIT31 0x80000000
+/* MCU notify host driver for coredump */
+#define FIRMWARE_INT_BIT15 0x00008000
+#define TX_FIFO_OVERFLOW 0x00000100
+#define FW_INT_IND_INDICATOR 0x00000080
+#define TX_COMPLETE_COUNT 0x00000070
+#define TX_UNDER_THOLD 0x00000008
+#define TX_EMPTY 0x00000004
+#define RX_DONE 0x00000002
+#define FW_OWN_BACK_INT 0x00000001
+
+/* MCU address offset */
+#define MCU_ADDRESS_OFFSET_CMD 12
+#define MCU_ADDRESS_OFFSET_EVT 16
+
+/* wifi CR */
+#define CONDBGCR 0x0034
+#define CONDBGCR_SEL 0x0040
+#define SDIO_CTRL_EN (1 << 31)
+#define WM_MONITER_SEL (~(0x40000000))
+#define PC_MONITER_SEL (~(0x20000000))
+#define PC_IDX_SWH(val, idx) ((val & (~(0x3F << 16))) | ((0x3F & idx) << 16))
+
+typedef int (*pdwnc_func) (u8 fgReset);
+typedef int (*reset_func_ptr2) (unsigned int gpio, int init_value);
+typedef int (*set_gpio_low)(u8 gpio);
+typedef int (*set_gpio_high)(u8 gpio);
+
+/**
+ * Send cmd dispatch evt
+ */
+#define HCI_EV_VENDOR 0xff
+#define SDIO_BLOCK_SIZE 512
+#define SDIO_RW_RETRY_COUNT 500
+#define MTK_SDIO_PACKET_HEADER_SIZE 4
+
+/* Driver & FW own related */
+#define DRIVER_OWN 0
+#define FW_OWN 1
+#define SET_OWN_LOOP_COUNT 10
+
+/* CMD&Event sent by driver */
+#define READ_REGISTER_CMD_LEN 16
+#define READ_REGISTER_EVT_HDR_LEN 11
+
+#define FW_ASSERT_CMD_LEN 4
+#define FW_ASSERT_CMD1_LEN 9
+#define NOTIFY_ALT_EVT_LEN 7
+
+#define READ_ADDRESS_EVT_HDR_LEN 7
+#define READ_ADDRESS_EVT_PAYLOAD_OFFSET 7
+#define WOBLE_DEBUG_EVT_TYPE 0xE8
+#define BLE_EVT_TYPE 0x3E
+
+#define LD_PATCH_CMD_LEN 10
+#define LD_PATCH_EVT_LEN 8
+
+#define CHECK_THREAD_RETRY_TIMES 50
+
+struct btmtk_sdio_hdr {
+ /* For SDIO Header */
+ __le16 len;
+ __le16 reserved;
+ /* For hci type */
+ u8 bt_type;
+} __packed;
+
+struct btmtk_sdio_thread {
+ struct task_struct *task;
+ wait_queue_head_t wait_q;
+ void *priv;
+ atomic_t thread_status;
+};
+
+struct btmtk_sdio_dev {
+ struct sdio_func *func;
+ struct btmtk_dev *bdev;
+
+ bool patched;
+ bool no_fw_own;
+ atomic_t int_count;
+ atomic_t tx_rdy;
+
+ /* TODO, need to confirm the max size of urb data, also need to confirm
+ * whether intr_complete and bulk_complete and soc_complete can all share
+ * this urb_transfer_buf
+ */
+ unsigned char *transfer_buf;
+ unsigned char *sdio_packet;
+
+ struct sk_buff_head tx_queue;
+ struct btmtk_sdio_thread sdio_thread;
+ struct btmtk_woble bt_woble;
+ struct btmtk_buffer_mode_struct *buffer_mode;
+
+ struct timer_list fw_own_timer;
+ atomic_t fw_own_timer_flag;
+};
+
+int btmtk_sdio_read_bt_mcu_pc(u32 *val);
+int btmtk_sdio_read_conn_infra_pc(u32 *val);
+
+#endif
diff --git a/include/uart/serdev/btmtk_uart_serdev.h b/include/uart/serdev/btmtk_uart_serdev.h
new file mode 100755
index 0000000..e5eb88f
--- /dev/null
+++ b/include/uart/serdev/btmtk_uart_serdev.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef _BTMTK_UART_H_
+#define _BTMTK_UART_H_
+#include <linux/serdev.h>
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_buffer_mode.h"
+
+#ifndef UART_DEBUG
+#define UART_DEBUG 0
+#endif
+
+/**
+ * Card-relate definition.
+ */
+#define HCI_HEADER_LEN 4
+
+#define MTK_STP_TLR_SIZE 2
+#define STP_HEADER_LEN 4
+#define STP_HEADER_CRC_LEN 2
+#define HCI_MAX_COMMAND_SIZE 255
+#define MAX_BUFFER_SIZE (4*1024)
+
+/* CMD&Event sent by driver */
+#define READ_REGISTER_CMD_LEN 16
+#define READ_REGISTER_EVT_HDR_LEN 11
+
+/* MCU address offset */
+#define MCU_ADDRESS_OFFSET_CMD 12
+#define MCU_ADDRESS_OFFSET_EVT 16
+
+typedef int (*pdwnc_func) (u8 fgReset);
+typedef int (*reset_func_ptr2) (unsigned int gpio, int init_value);
+typedef int (*set_gpio_low)(u8 gpio);
+typedef int (*set_gpio_high)(u8 gpio);
+
+/**
+ * Send cmd dispatch evt
+ */
+#define HCI_EV_VENDOR 0xff
+
+#define READ_ADDRESS_EVT_HDR_LEN 7
+#define READ_ADDRESS_EVT_PAYLOAD_OFFSET 7
+#define WOBLE_DEBUG_EVT_TYPE 0xE8
+
+#define LD_PATCH_CMD_LEN 10
+#define LD_PATCH_EVT_LEN 8
+
+struct btmtk_uart_dev {
+ struct serdev_device *serdev;
+ struct clk *clk;
+ struct clk *osc;
+ unsigned char *transfer_buf;
+};
+#endif
diff --git a/include/uart/tty/btmtk_uart_tty.h b/include/uart/tty/btmtk_uart_tty.h
new file mode 100755
index 0000000..f065d02
--- /dev/null
+++ b/include/uart/tty/btmtk_uart_tty.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef _BTMTK_UART_H_
+#define _BTMTK_UART_H_
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_buffer_mode.h"
+#include "btmtk_woble.h"
+#include "btmtk_chip_reset.h"
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/serial.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/clk.h>
+#include <linux/suspend.h>
+#if (USE_DEVICE_NODE == 1)
+#include "btmtk_proj_sp.h"
+#endif
+
+#define HCI_HEADER_LEN 4
+
+struct mtk_stp_hdr {
+ u8 prefix;
+ __be16 dlen;
+ u8 cs;
+} __packed;
+#define MTK_STP_TLR_SIZE 2
+#define STP_HEADER_LEN 4
+#define STP_HEADER_CRC_LEN 2
+
+#define BTMTKUART_FLAG_STANDALONE_HW BIT(0)
+
+/* CMD&Event sent by driver */
+#define READ_REGISTER_CMD_LEN 16
+#define READ_REGISTER_EVT_HDR_LEN 11
+
+#define WRITE_REGISTER_CMD_LEN 24
+#define WRITE_REGISTER_EVT_HDR_LEN 11
+
+/* MCU address offset */
+#define MCU_ADDRESS_OFFSET_CMD 12
+#define MCU_ADDRESS_OFFSET_EVT 16
+
+/* MCU value offset */
+#define MCU_VALUE_OFFSET_CMD 16
+
+/* Pimux Address and Value */
+#define BT_PINMUX_CTRL_REG 0x70005054
+#define BT_SUBSYS_RST_PINMUX 0x00000020
+#define BT_CTSRTS_PINMUX 0x00044000
+#define BT_PINMUX_CTRL_ENABLE (BT_SUBSYS_RST_PINMUX | BT_CTSRTS_PINMUX)
+
+#define BT_SUBSYS_RST_REG 0x70002610
+#define BT_SUBSYS_RST_ENABLE 0x00000080
+
+#define BT_REG_LEN 4
+#define BT_REG_VALUE_LEN 4
+
+/* MCU baud define */
+#define BT_FLOWCTRL_OFFSET 12
+#define BT_NONE_FC 0x00
+#define BT_HW_FC 0x40
+#define BT_SW_FC 0x80
+
+/**
+ * Send cmd dispatch evt
+ */
+#define HCI_EV_VENDOR 0xff
+
+#define READ_ADDRESS_EVT_HDR_LEN 7
+#define READ_ADDRESS_EVT_PAYLOAD_OFFSET 7
+#define WOBLE_DEBUG_EVT_TYPE 0xE8
+
+#define LD_PATCH_CMD_LEN 10
+#define LD_PATCH_EVT_LEN 8
+
+#define SETBAUD_CMD_LEN 14
+#define SETBAUD_EVT_LEN 9
+
+#define GETBAUD_CMD_LEN 9
+#define GETBAUD_EVT_LEN 9
+#define BAUD_SIZE 4
+
+#define WAKEUP_CMD_LEN 5
+#define WAKEUP_EVT_LEN 9
+
+#if (USE_DEVICE_NODE == 0)
+#define FWOWN_CMD_LEN 9
+#define DRVOWN_CMD_LEN 1
+#define OWNTYPE_EVT_LEN 9
+#else
+#define FWOWN_CMD_LEN 10
+#define DRVOWN_CMD_LEN 1
+#define OWNTYPE_EVT_LEN 10
+#endif
+
+#define BTMTK_MAX_SEND_RETRY 10000
+
+#define BT_UART_DEFAULT_BAUD 115200
+
+/* Delay time between subsys reset GPIO pull low/high */
+#define SUBSYS_RESET_GPIO_DELAY_TIME 50
+
+/* Delay time after write data to io_buf */
+#define IO_BUF_DELAY_TIME 50
+
+typedef int (*pdwnc_func) (u8 fgReset);
+typedef int (*reset_func_ptr2) (unsigned int gpio, int init_value);
+typedef int (*set_gpio_low)(u8 gpio);
+typedef int (*set_gpio_high)(u8 gpio);
+
+enum UART_FC {
+ UART_DISABLE_FC = 0, /*NO flow control*/
+ /*MTK SW Flow Control, differs from Linux Flow Control*/
+ UART_MTK_SW_FC = 1,
+ UART_LINUX_FC = 2, /*Linux SW Flow Control*/
+ UART_HW_FC = 3, /*HW Flow Control*/
+};
+
+struct UART_CONFIG {
+ enum UART_FC fc;
+ int parity;
+ int stop_bit;
+ int iBaudrate;
+};
+
+struct btmtk_uart_data {
+ unsigned int flags;
+ const char *fwname;
+};
+
+struct btmtk_uart_dev {
+ struct hci_dev *hdev;
+ struct tty_struct *tty;
+ unsigned long hdev_flags;
+
+ /* For tx queue */
+ struct sk_buff_head tx_queue;
+ spinlock_t tx_lock;
+ struct task_struct *tx_task;
+ atomic_t thread_status;
+ unsigned long tx_state;
+
+ /* For rx queue */
+ struct sk_buff *rx_skb;
+ unsigned long rx_state;
+
+ struct sk_buff *evt_skb;
+ wait_queue_head_t p_wait_event_q;
+
+ unsigned int subsys_reset;
+ unsigned int dongle_state;
+ unsigned int uart_baudrate_set;
+
+ u8 stp_pad[6];
+ u8 stp_cursor;
+ u16 stp_dlen;
+
+ struct UART_CONFIG uart_cfg;
+ struct btmtk_woble bt_woble;
+
+ /* config form dts*/
+ u32 baudrate;
+ u32 hub_en;
+ u32 sleep_en;
+ u32 flush_en;
+
+ /* For uarthub setting */
+ u8 fw_hub_en;
+ u8 crc_en;
+ u8 rhw_en;
+ u8 fw_dl_ready;
+
+ /* driver,fw own */
+ bool no_fw_own;
+ u8 own_state;
+ struct timer_list fw_own_timer;
+ atomic_t fw_own_timer_flag;
+ atomic_t need_drv_own;
+ atomic_t fw_wake;
+
+ /* sempaphore to compare event */
+ struct semaphore evt_comp_sem;
+
+#if (USE_DEVICE_NODE == 1)
+ /* dynamic tx power control */
+ struct btmtk_dypwr_st dy_pwr;
+#endif
+};
+
+#define btmtk_uart_is_standalone(bdev) \
+ ((bdev)->data->flags & BTMTKUART_FLAG_STANDALONE_HW)
+#define btmtk_uart_is_builtin_soc(bdev) \
+ !((bdev)->data->flags & BTMTKUART_FLAG_STANDALONE_HW)
+
+
+/**
+ * Maximum rom patch file name length
+ */
+
+#define N_MTK (15+1)
+/**
+ * Upper layeard IOCTL
+ */
+#define HCIUARTSETPROTO _IOW('U', 200, int)
+#define HCIUARTSETBAUD _IOW('U', 201, int)
+#define HCIUARTGETBAUD _IOW('U', 202, int)
+#define HCIUARTSETSTP _IOW('U', 203, int)
+#define HCIUARTLOADPATCH _IOW('U', 204, int)
+#define HCIUARTSETWAKEUP _IOW('U', 205, int)
+#define HCIUARTINIT _IOW('U', 206, int)
+
+/**
+ * parameter settings
+ */
+#define BTMTK_HUB_EN (1 << 0)
+#define BTMTK_SLEEP_EN (1 << 1)
+
+
+//int btmtk_cif_send_calibration(struct hci_dev *hdev);
+#endif
+
diff --git a/include/usb/btmtk_usb.h b/include/usb/btmtk_usb.h
new file mode 100644
index 0000000..48a644d
--- /dev/null
+++ b/include/usb/btmtk_usb.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#ifndef _BTMTK_USB_H_
+#define _BTMTK_USB_H_
+#include <linux/usb.h>
+#include "btmtk_define.h"
+#include "btmtk_main.h"
+#include "btmtk_woble.h"
+#include "btmtk_chip_reset.h"
+
+#define HCI_MAX_COMMAND_SIZE 255
+#define URB_MAX_BUFFER_SIZE (4*1024)
+
+#define BT0_MCU_INTERFACE_NUM 0
+#define BT1_MCU_INTERFACE_NUM 3
+#define BT_MCU_INTERFACE_NUM_MAX 4
+#define BT_MCU_NUM_MAX 2
+
+typedef int (*pdwnc_func) (u8 fgReset);
+typedef int (*reset_func_ptr2) (unsigned int gpio, int init_value);
+typedef int (*set_gpio_low)(u8 gpio);
+typedef int (*set_gpio_high)(u8 gpio);
+
+/**
+ * Send cmd dispatch evt
+ */
+#define HCI_EV_VENDOR 0xff
+#define HCI_USB_IO_BUF_SIZE 256
+
+
+/* UHW CR mapping */
+#define BT_MISC 0x70002510
+#define MCU_BT0_INIT_DONE (0x1 << 8)
+#define MCU_BT1_INIT_DONE (0x1 << 9)
+#define BT_SUBSYS_RST 0x70002610
+#define BT_SUBSYS_RST_6639 0x70028610
+#define UDMA_INT_STA_BT 0x74000024
+#define UDMA_INT_STA_BT1 0x74000308
+#define BT_WDT_STATUS 0x740003A0
+#define EP_RST_OPT 0x74011890
+#define EP_RST_IN_OUT_OPT 0x00010001
+
+#define BT_GDMA_DONE_ADDR_W 0x74000A0C
+#define BT_GDMA_DONE_7921_VALUE_W 0x00403FA9
+#define BT_GDMA_DONE_7922_VALUE_W 0x00403EA9
+#define BT_GDMA_DONE_7902_VALUE_W 0x00403EA9
+#define BT_GDMA_DONE_ADDR_R 0x74000A08
+#define BT_GDMA_DONE_VALUE_R 0xFFFFFFFB /* bit2: 0 - dma done, 1 - dma doing */
+#define BT_GDMA_DONE_6639_VALUE_W 0xC0040900
+#define BT_GDMA_DONE_6639_ADDR_W 0x18023A0C
+#define BT_GDMA_DONE_6639_ADDR_R 0x18023A10
+#define BT_GDMA_DONE_6639_VALUE_R 0xBFFFFFFF /* bit30: 0 - dma done, 1 - dma doing */
+
+
+/* CMD&Event sent by driver */
+#define NOTIFY_ALT_EVT_LEN 7
+
+#define LD_PATCH_CMD_LEN 9
+#define LD_PATCH_EVT_LEN 8
+
+#define READ_ADDRESS_EVT_HDR_LEN 7
+#define READ_ADDRESS_EVT_PAYLOAD_OFFSET 7
+#define WOBLE_DEBUG_EVT_TYPE 0xE8
+#define BLE_EVT_TYPE 0x3E
+
+#define WMT_TRIGGER_ASSERT_LEN 9
+
+struct btmtk_cif_chip_reset {
+ /* For Whole chip reset */
+ pdwnc_func pf_pdwndFunc;
+ reset_func_ptr2 pf_resetFunc2;
+ set_gpio_low pf_lowFunc;
+ set_gpio_high pf_highFunc;
+};
+
+struct btmtk_usb_dev {
+ struct usb_endpoint_descriptor *intr_ep;
+ /* EP10 OUT */
+ struct usb_endpoint_descriptor *intr_iso_tx_ep;
+ /* EP10 IN */
+ struct usb_endpoint_descriptor *intr_iso_rx_ep;
+ /* BULK CMD EP1 OUT or EP 11 OUT */
+ struct usb_endpoint_descriptor *bulk_cmd_tx_ep;
+ /* EP15 in for reset */
+ struct usb_endpoint_descriptor *reset_intr_ep;
+ struct usb_endpoint_descriptor *bulk_tx_ep;
+ struct usb_endpoint_descriptor *bulk_rx_ep;
+ struct usb_endpoint_descriptor *isoc_tx_ep;
+ struct usb_endpoint_descriptor *isoc_rx_ep;
+
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ struct usb_interface *isoc;
+ struct usb_interface *iso_channel;
+
+
+ struct usb_anchor tx_anchor;
+ struct usb_anchor intr_anchor;
+ struct usb_anchor bulk_anchor;
+ struct usb_anchor isoc_anchor;
+ struct usb_anchor ctrl_anchor;
+ struct usb_anchor ble_isoc_anchor;
+
+ __u8 cmdreq_type;
+ __u8 cmdreq;
+
+ int new_isoc_altsetting;
+ int new_isoc_altsetting_interface;
+
+ unsigned char *o_usb_buf;
+
+ unsigned char *urb_intr_buf;
+ unsigned char *urb_bulk_buf;
+ unsigned char *urb_ble_isoc_buf;
+ struct btmtk_woble bt_woble;
+};
+
+#endif
diff --git a/init.btmtk_sdio.rc b/init.btmtk_sdio.rc
new file mode 100644
index 0000000..ce9549c
--- /dev/null
+++ b/init.btmtk_sdio.rc
@@ -0,0 +1,3 @@
+# load btmtksdio
+on boot
+ insmod /vendor/lib/modules/btmtk_sdio_unify.ko
diff --git a/init.btmtk_uart.rc b/init.btmtk_uart.rc
new file mode 100644
index 0000000..6cdfd03
--- /dev/null
+++ b/init.btmtk_uart.rc
@@ -0,0 +1,20 @@
+# load btmtksdio
+#on boot
+# insmod /vendor/lib/modules/btmtk_uart_unify.ko
+# start uart_launcher
+
+on property:vendor.connsys.driver.ready=yes && property:ro.vendor.bt.connac3=yes
+ insmod /vendor/lib/modules/btmtk_uart_unify.ko
+ #chown bluetooth bluetooth /proc/driver/bt_dbg
+ start uart_launcher
+
+on property:ro.vendor.bt.connac3=no
+ stop uart_launcher
+
+on property:vendor.connsys.driver.ready=no
+ stop uart_launcher
+
+# service for uart_launcher
+service uart_launcher /vendor/bin/uart_launcher -f 1 -p /dev/ttyS3
+ user root
+ group system
diff --git a/makefile_example/ALPS/Android.mk b/makefile_example/ALPS/Android.mk
new file mode 100644
index 0000000..f11b35d
--- /dev/null
+++ b/makefile_example/ALPS/Android.mk
@@ -0,0 +1,84 @@
+# Copyright Statement:
+#
+# This software/firmware and related documentation ("MediaTek Software") are
+# protected under relevant copyright laws. The information contained herein
+# is confidential and proprietary to MediaTek Inc. and/or its licensors.
+# Without the prior written permission of MediaTek inc. and/or its licensors,
+# any reproduction, modification, use or disclosure of MediaTek Software,
+# and information contained herein, in whole or in part, shall be strictly prohibited.
+#
+# MediaTek Inc. (C) 2017. All rights reserved.
+#
+# BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+# THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+# RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
+# AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+# NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+# SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+# SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
+# THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
+# THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
+# CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
+# SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
+# STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
+# CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+# AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+# OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
+# MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+#
+# The following software/firmware and/or related documentation ("MediaTek Software")
+# have been modified by MediaTek Inc. All revisions are subject to any receiver's
+# applicable license agreements with MediaTek Inc.
+
+###############################################################################
+# Generally Android.mk can not get KConfig setting
+# we can use this way to get
+# include the final KConfig
+# but there is no $(AUTO_CONF) at the first time (no out folder) when make
+#
+#ifneq (,$(wildcard $(AUTO_CONF)))
+#include $(AUTO_CONF)
+#include $(CLEAR_VARS)
+#endif
+
+###############################################################################
+###############################################################################
+# Generally Android.mk can not get KConfig setting #
+# #
+# do not have any KConfig checking in Android.mk #
+# do not have any KConfig checking in Android.mk #
+# do not have any KConfig checking in Android.mk #
+# #
+# e.g. ifeq ($(CONFIG_MTK_COMBO_WIFI), m) #
+# xxxx #
+# endif #
+# #
+# e.g. ifneq ($(filter "MT6632",$(CONFIG_MTK_COMBO_CHIP)),) #
+# xxxx #
+# endif #
+# #
+# All the KConfig checking should move to Makefile for each module #
+# All the KConfig checking should move to Makefile for each module #
+# All the KConfig checking should move to Makefile for each module #
+# #
+###############################################################################
+###############################################################################
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(MTK_BT_SUPPORT),yes)
+ifneq ($(filter MTK_MT79%, $(MTK_BT_CHIP)),)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := btmtk_sdio_unify.ko
+LOCAL_PROPRIETARY_MODULE := true
+LOCAL_MODULE_OWNER := mtk
+LOCAL_INIT_RC := init.btmtk_sdio.rc
+LOCAL_SRC_FILES := $(patsubst $(LOCAL_PATH)/%,%,$(shell find $(LOCAL_PATH) -type f -name '*.[cho]')) Makefile
+
+include $(MTK_KERNEL_MODULE)
+
+endif
+endif
diff --git a/makefile_example/ALPS/init.btmtk_sdio.rc b/makefile_example/ALPS/init.btmtk_sdio.rc
new file mode 100644
index 0000000..ce9549c
--- /dev/null
+++ b/makefile_example/ALPS/init.btmtk_sdio.rc
@@ -0,0 +1,3 @@
+# load btmtksdio
+on boot
+ insmod /vendor/lib/modules/btmtk_sdio_unify.ko
diff --git a/makefile_example/DTV/Android.mk b/makefile_example/DTV/Android.mk
new file mode 100644
index 0000000..da1ccd7
--- /dev/null
+++ b/makefile_example/DTV/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(filter 3.10 3.18 4.9 4.19 5.4 5.4_12, $(subst linux-,,$(KERNEL_VER))),)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := btmtk_usb_unify.ko
+LOCAL_REQUIRED_MODULES := wlan_mt7961_reset.ko
+include $(MTK_KERNEL_MODULE)
+
+endif
\ No newline at end of file
diff --git a/makefile_example/DTV/Kbuild b/makefile_example/DTV/Kbuild
new file mode 100644
index 0000000..0d4f0f8
--- /dev/null
+++ b/makefile_example/DTV/Kbuild
@@ -0,0 +1,95 @@
+#################### Configurations ####################
+# Compile Options for bt driver configuration.
+CONFIG_SUPPORT_BT_DL_WIFI_PATCH=y
+CONFIG_SUPPORT_BLUEZ=n
+CONFIG_SUPPORT_DVT=n
+CONFIG_SUPPORT_HW_DVT=n
+CONFIG_SUPPORT_MULTI_DEV_NODE=n
+CONFIG_GKI_SUPPORT=y
+
+ifeq ($(CONFIG_GKI_SUPPORT), y)
+ccflags-y += -DCFG_CHIP_RESET_KO_SUPPORT
+ccflags-y += -I$(src)/../wlan_driver/gen4m_mt7961/reset/include/
+
+RESET_DIR = $(shell pwd)/../../KERNEL_MODULES/wlan_mt7961_reset.ko_intermediates/LINKED/Module.symvers
+$(info 'RESET_DIR=' $(RESET_DIR))
+KBUILD_EXTRA_SYMBOLS += $(RESET_DIR)
+endif
+
+
+ifeq ($(CONFIG_SUPPORT_BT_DL_WIFI_PATCH), y)
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=1
+else
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_BLUEZ), y)
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=1
+else
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_HW_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_DVT=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_MULTI_DEV_NODE), y)
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=1
+else
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=0
+endif
+
+ccflags-y += -DCONFIG_MP_WAKEUP_SOURCE_SYSFS_STAT
+
+ccflags-y += -DCHIP_IF_USB
+ccflags-y += -I$(src)/include/usb
+ccflags-y += -I$(src)/include/ -I$(src)
+
+#################### Configurations ####################
+# For chip interface, driver supports "usb", "sdio", "uart" and "btif"
+MTK_CHIP_IF := usb
+
+ifeq ($(MTK_CHIP_IF), sdio)
+ MOD_NAME = btmtk_sdio_unify
+ CFILES := sdio/btmtksdio.c btmtk_woble.c btmtk_buffer_mode.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_SDIO
+ ccflags-y += -DSDIO_DEBUG=0
+ ccflags-y += -I$(src)/include/sdio
+else ifeq ($(MTK_CHIP_IF), usb)
+ MOD_NAME = btmtk_usb_unify
+ CFILES := usb/btmtkusb.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_USB
+ ccflags-y += -I$(src)/include/usb
+else ifeq ($(MTK_CHIP_IF), uart_tty)
+ MOD_NAME = btmtk_uart_unify
+ CFILES := uart/btmtktty.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_UART_TTY
+ ccflags-y += -I$(src)/include/uart/tty
+else ifeq ($(MTK_CHIP_IF), uart_serdev)
+ MOD_NAME = btmtk_uart_unify
+ ccflags-y += -DCHIP_IF_UART_SERDEV
+ CFILES := uart/btmtkserdev.c
+ ccflags-y += -I$(src)/include/uart/serdev
+else
+ MOD_NAME = btmtkbtif_unify
+ CFILES := btif/btmtk_btif.c
+ ccflags-y += -DCHIP_IF_BTIF
+ ccflags-y += -I$(src)/include/btif
+endif
+
+CFILES += btmtk_main.c btmtk_fw_log.c
+
+ccflags-y += -I$(src)/include/ -I$(src)/
+
+$(MOD_NAME)-objs := $(CFILES:.c=.o)
+
+obj-m += $(MOD_NAME).o
+
diff --git a/makefile_example/DTV/Makefile b/makefile_example/DTV/Makefile
new file mode 100644
index 0000000..5963a79
--- /dev/null
+++ b/makefile_example/DTV/Makefile
@@ -0,0 +1,179 @@
+#export KERNEL_DIR := /lib/modules/$(shell uname -r)/build
+
+###############################################################################
+# apollo system build
+###############################################################################
+include $(LINUX_ROOT)/linux_core/driver/ko/target/target.mak
+
+
+ifeq "$(UTOPIA)" "true"
+KERNEL_DIR := $(UTOPIA_KERNEL_ROOT)/$(subst linux-,,$(KERNEL_VER))
+KERNEL_SRC := $(UTOPIA_KERNEL_ROOT)/$(subst linux-,,$(KERNEL_VER))
+else
+KERNEL_DIR=$(KERNEL_OBJ_ROOT)/$(KERNEL_VER)/$(KERNEL_CONFIG)_modules
+KERNEL_SRC=$(KERNEL_ROOT)/$(KERNEL_VER)
+endif
+CURRENT_DIR=$(PWD)/../../../linux_mts/ko_modules/bt_driver_linux_v2
+#KERNEL_DIR=/proj/mtk94020/Capy_buzzard_0429/out/mediatek_linux/output/mediatek/mt5895_cn/rel/obj/kernel/linux_core/kernel/4.9/mt5895_android_smp_mod_defconfig_modules
+#KERNEL_SRC=/proj/mtk94020/Capy_buzzard_0429/kernel/zodiac/4.9
+#CURRENT_DIR=$(PWD)
+
+
+#################### Configurations ####################
+# Compile Options for bt driver configuration.
+CONFIG_SUPPORT_BT_DL_WIFI_PATCH=y
+CONFIG_SUPPORT_BLUEZ=n
+CONFIG_SUPPORT_DVT=n
+CONFIG_SUPPORT_HW_DVT=n
+CONFIG_SUPPORT_MULTI_DEV_NODE=n
+
+ifeq ($(CONFIG_SUPPORT_BT_DL_WIFI_PATCH), y)
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=1
+else
+ ccflags-y += -DCFG_SUPPORT_BT_DL_WIFI_PATCH=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_BLUEZ), y)
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=1
+else
+ ccflags-y += -DCFG_SUPPORT_BLUEZ=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_HW_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_HW_DVT=0
+endif
+
+ifeq ($(SUPPORT_WAKEUP_IRQ), yes)
+ ccflags-y += -DCFG_SUPPORT_WAKEUP_IRQ
+endif
+
+ifeq ($(CONFIG_SUPPORT_DVT), y)
+ ccflags-y += -DCFG_SUPPORT_DVT=1
+else
+ ccflags-y += -DCFG_SUPPORT_DVT=0
+endif
+
+ifeq ($(CONFIG_SUPPORT_MULTI_DEV_NODE), y)
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=1
+else
+ ccflags-y += -DCFG_SUPPORT_MULTI_DEV_NODE=0
+endif
+
+#################### Configurations ####################
+# For chip interface, driver supports "usb", "sdio", "uart_tty", "uart_serdev" and "btif"
+MTK_CHIP_IF := usb
+
+ifeq ($(MTK_CHIP_IF), sdio)
+ MOD_NAME = btmtk_sdio_unify
+ CFILES := sdio/btmtksdio.c btmtk_woble.c btmtk_buffer_mode.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_SDIO
+ ccflags-y += -DSDIO_DEBUG=0
+ ccflags-y += -I$(src)/include/sdio
+else ifeq ($(MTK_CHIP_IF), usb)
+ MOD_NAME = btmtk_usb_unify
+ CFILES := usb/btmtkusb.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_USB
+ ccflags-y += -I$(src)/include/usb
+else ifeq ($(MTK_CHIP_IF), uart_tty)
+ MOD_NAME = btmtk_uart_unify
+ CFILES := uart/btmtktty.c btmtk_woble.c btmtk_chip_reset.c
+ ccflags-y += -DCHIP_IF_UART_TTY
+ ccflags-y += -I$(src)/include/uart/tty
+else ifeq ($(MTK_CHIP_IF), uart_serdev)
+ MOD_NAME = btmtk_uart_unify
+ ccflags-y += -DCHIP_IF_UART_SERDEV
+ CFILES := uart/btmtkserdev.c
+ ccflags-y += -I$(src)/include/uart/serdev
+else
+ MOD_NAME = btmtkbtif_unify
+ CFILES := btif/btmtk_btif.c
+ ccflags-y += -DCHIP_IF_BTIF
+ ccflags-y += -I$(src)/include/btif
+endif
+
+CFILES += btmtk_main.c btmtk_fw_log.c
+
+ccflags-y += -I$(src)/include/ -I$(KERNEL_SRC)/include/ -I$(KERNEL_SRC)/drivers/bluetooth
+
+ccflags-y += -Werror
+
+$(MOD_NAME)-objs := $(CFILES:.c=.o)
+
+obj-m += $(MOD_NAME).o
+
+
+#VPATH = /opt/toolchains/gcc-linaro-aarch64-linux-gnu-4.9-2014.09_linux
+#UART_MOD_NAME = btmtk_uart
+#UART_CFILES := \
+# btmtk_uart_main.c
+#$(UART_MOD_NAME)-objs := $(UART_CFILES:.c=.o)
+###############################################################################
+# Common
+###############################################################################
+#obj-m := $(UART_MOD_NAME).o
+all:
+ $(warning ======================================================)
+ $(warning Make MTK BT Driver Unify, btmtk_usb_unify.ko)
+ $(warning KERNEL_DIR=$(KERNEL_DIR))
+ $(warning CURRENT_DIR=$(CURRENT_DIR))
+ $(warning CC=$(CC))
+ $(warning CROSS_COMPILE=$(CROSS_COMPILE))
+ $(warning TARGET_IC=$(TARGET_IC))
+ $(warning KERNEL_VER=$(KERNEL_VER))
+ $(warning KERNEL_CONFIG=$(KERNEL_CONFIG))
+ $(warning OBJ_ROOT=$(OBJ_ROOT))
+ $(warning OUTPUT_ROOT=$(OUTPUT_ROOT))
+ $(warning ccflags-y = $(ccflags-y))
+ $(warning ======================================================)
+ifeq "$(UTOPIA)" "true"
+ make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules CROSS_COMPILE=$(KERNEL_CROSS_COMPILE)
+ if [ -d $(OUTPUT_ROOT)/kernel_modules ]; then \
+ cp -f $(CURRENT_DIR)/btmtk_usb_unify.ko $(OUTPUT_ROOT)/kernel_modules/btmtk_usb_unify.ko; \
+ fi
+else
+ make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
+ if [ -d $(OUTPUT_ROOT)/kernel_modules ]; then \
+ cp -f $(OBJ_ROOT)/linux_mts/ko_modules/bt_driver_linux_v2/btmtk_usb_unify.ko $(OUTPUT_ROOT)/kernel_modules/btmtk_usb_unify.ko; \
+ fi
+endif
+
+clean:
+ $(warning ======================================================)
+ $(warning Clean MTK BT Driver Unify, btmtk_usb_unify.ko)
+ $(warning KERNEL_DIR=$(KERNEL_DIR))
+ $(warning CURRENT_DIR=$(CURRENT_DIR))
+ $(warning OBJ_ROOT=$(OBJ_ROOT))
+ $(warning OUTPUT_ROOT=$(OUTPUT_ROOT))
+ $(warning ======================================================)
+ if [ -d $(OUTPUT_ROOT)/kernel_modules ]; then \
+ rm -f $(OUTPUT_ROOT)/kernel_modules/btmtk_usb_unify.ko; \
+ fi
+ if [ -d $(KERNEL_DIR) ]; then \
+ make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean; \
+ fi
+# Check coding style
+# export IGNORE_CODING_STYLE_RULES := NEW_TYPEDEFS,LEADING_SPACE,CODE_INDENT,SUSPECT_CODE_INDENT
+ccs:
+ ./util/checkpatch.pl -f ./sdio/btmtksdio.c
+ ./util/checkpatch.pl -f ./include/sdio/btmtk_sdio.h
+ ./util/checkpatch.pl -f ./include/btmtk_define.h
+ ./util/checkpatch.pl -f ./include/btmtk_drv.h
+ ./util/checkpatch.pl -f ./include/btmtk_chip_if.h
+ ./util/checkpatch.pl -f ./include/btmtk_main.h
+ ./util/checkpatch.pl -f ./include/btmtk_buffer_mode.h
+ ./util/checkpatch.pl -f ./include/uart/tty/btmtk_uart_tty.h
+ ./util/checkpatch.pl -f ./uart/btmtktty.c
+ ./util/checkpatch.pl -f ./include/btmtk_fw_log.h
+ ./util/checkpatch.pl -f ./include/btmtk_woble.h
+ ./util/checkpatch.pl -f ./include/uart/btmtk_uart.h
+ ./util/checkpatch.pl -f ./uart/btmtk_uart_main.c
+ ./util/checkpatch.pl -f ./include/usb/btmtk_usb.h
+ ./util/checkpatch.pl -f ./usb/btmtkusb.c
+ ./util/checkpatch.pl -f btmtk_fw_log.c
+ ./util/checkpatch.pl -f btmtk_main.c
+ ./util/checkpatch.pl -f btmtk_buffer_mode.c
+ ./util/checkpatch.pl -f btmtk_woble.c
+ ./util/checkpatch.pl -f btmtk_chip_reset.c
+
diff --git a/makefile_example/DTV/Makefile.ce b/makefile_example/DTV/Makefile.ce
new file mode 100644
index 0000000..d9a44aa
--- /dev/null
+++ b/makefile_example/DTV/Makefile.ce
@@ -0,0 +1,200 @@
+# Makefile for MT76x8 combo driver
+
+##############################################################
+# Common settings
+##############################################################
+
+#BT_CHIP_ID := mt79xx
+
+PWD=$(shell pwd)
+DRIVER_DIR=$(PWD)
+
+MODULE_NAME := btmtk_usb_unify
+
+
+##############################################################
+# Platform specific
+##############################################################
+# Set default platform
+PLATFORM:=MT53XX
+
+ifeq ($(PLATFORM),MT53XX)
+TARGET = LINUX
+
+# Sets DRIVER_PARTIAL_BUILD to y to compile driver only, otherwise set to n
+DRIVER_PARTIAL_BUILD := n
+
+#
+# [ Driver Partial Build ]
+#
+ifeq ($(DRIVER_PARTIAL_BUILD),y)
+# Please adjust the following variables according to your build attempt
+ifeq ($(ANDROID),)
+ ANDROID=false
+endif
+64BIT_MODE := false
+LINUX_ROOT ?= $(word 1, $(subst /apollo/,/apollo /, $(shell pwd -L)))
+
+ifeq ($(ANDROID),true)
+# -> Android, driver partial build
+ SYSBUILD_NAME=m-base
+ RELEASE_NAME=mediatek/mt5891_eu_64
+ KERNEL_VER=3.18
+ KERNEL_CONFIG=mt5891_android_smp_mod_defconfig
+ ifeq ($(64BIT_MODE),true)
+ export 64BIT_MODE=true
+ export KERNEL_64BIT=true
+ export TOOL_CHAIN_64BIT=4.9.3
+ endif
+ifeq "$(UTOPIA)" "true"
+ LINUX_SRC = $(UTOPIA_KERNEL_ROOT)/$(subst linux-,,$(KERNEL_VER))
+else
+ LINUX_SRC=$(LINUX_ROOT)/../android/$(SYSBUILD_NAME)/out/mediatek_linux/output/$(RELEASE_NAME)/rel/obj/kernel/linux_core/kernel/linux-$(KERNEL_VER)/$(KERNEL_CONFIG)_modules
+endif
+ OBJ_ROOT ?= $(LINUX_ROOT)/../android/$(SYSBUILD_NAME)/out/mediatek_linux/output/$(RELEASE_NAME)/rel/obj
+ export KERNEL_OBJ_ROOT=$(LINUX_SRC)/../..
+
+else
+# -> Linux, driver partial build
+# !NOTE! Edit the following parameters!
+ SYSBUILD_NAME := sys_build/XXXX
+ RELEASE_NAME=mt5891_eu
+ KERNEL_VER=3.10?
+ KERNEL_CONFIG=mt5891_smp_mod_defconfig
+ ifeq ($(64BIT_MODE),true)
+ export 64BIT_MODE=true
+ export KERNEL_64BIT=true
+ export TOOL_CHAIN_64BIT=4.9.3
+ endif
+ifeq "$(UTOPIA)" "true"
+ LINUX_SRC = $(UTOPIA_KERNEL_ROOT)/$(subst linux-,,$(KERNEL_VER))
+else
+ LINUX_SRC=$(LINUX_ROOT)/$(SYSBUILD_NAME)/$(RELEASE_NAME)/rel_obj/kernel/linux_core/kernel/linux-$(KERNEL_VER)/$(KERNEL_CONFIG)_modules
+endif
+ OBJ_ROOT := $(LINUX_ROOT)/$(SYSBUILD_NAME)/$(RELEASE_NAME)/rel_obj
+ export KERNEL_OBJ_ROOT=$(LINUX_SRC)/../..
+ CROSS_COMPILE=$(KERNEL_CROSS_COMPILE)
+
+endif
+
+
+#
+# [ Sys build ]
+#
+else
+ifeq "$(UTOPIA)" "true"
+ LINUX_SRC = $(UTOPIA_KERNEL_ROOT)/$(subst linux-,,$(KERNEL_VER))
+ OUT_STA_KO_DIR = $(LINUX_ROOT)/linux_mts/ko_modules/bt_driver_linux_v2
+ OUT_STA_KO = $(LINUX_ROOT)/linux_mts/ko_modules/bt_driver_linux_v2/$(MODULE_NAME).ko
+else
+ LINUX_SRC=$(KERNEL_OBJ_ROOT)/$(KERNEL_VER)/$(KERNEL_CONFIG)_modules
+ # overwrite $(OUT_STA_KO) for MT53XX platform
+ OUT_STA_KO_DIR = $(OBJ_ROOT)/linux_mts/ko_modules/bt_driver_linux_v2
+ OUT_STA_KO = $(OBJ_ROOT)/linux_mts/ko_modules/bt_driver_linux_v2/$(MODULE_NAME).ko
+endif
+include $(LINUX_ROOT)/linux_mts/build/mak/toolchain.mak
+endif
+CROSS_COMPILE=$(KERNEL_CROSS_COMPILE)
+
+ifeq ($(CROSS_COMPILE),)
+CROSS_COMPILE=$(KERNEL_CROSS_COMPILE)
+endif
+ifeq "$(CC)" "gcc"
+CC ?= $(CROSS_COMPILE)gcc
+endif
+
+$(warning =============================================)
+$(warning bt driver unify LINUX_SRC=$(LINUX_SRC))
+$(warning CROSS_COMPILE=$(CROSS_COMPILE))
+$(warning KERNEL_CROSS_COMPILE=$(KERNEL_CROSS_COMPILE))
+$(warning =============================================)
+
+endif
+#- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# MT53XX end
+#= = = = = = = = = = = = = = = = = = = = = = = = = = = =
+
+
+##############################################################
+# Compile options
+##############################################################
+
+
+##############################################################
+# Compile settings
+##############################################################
+
+all: driver
+
+driver:
+ifeq "$(UTOPIA)" "true"
+ +cd $(DRIVER_DIR) && make -C $(LINUX_SRC) M=$(DRIVER_DIR) PLATFORM_FLAGS="$(PLATFORM_FLAGS)" MTK_CHIP_IF=usb modules CROSS_COMPILE=$(KERNEL_CROSS_COMPILE)
+else
+ $(warning bt driver unify1111 LINUX_SRC=$(LINUX_SRC))
+ +cd $(DRIVER_DIR) && make -C $(LINUX_SRC) M=$(DRIVER_DIR) PLATFORM_FLAGS="$(PLATFORM_FLAGS)" MTK_CHIP_IF=usb modules
+endif
+ifeq ($(PLATFORM),MSTAR)
+ $(CROSS_COMPILE)strip --strip-unneeded $(MODULE_NAME).ko
+endif
+ifeq ($(PLATFORM),MT53XX)
+ifeq ($(DRIVER_PARTIAL_BUILD), y)
+ @cd $(DRIVER_DIR) && $(CROSS_COMPILE)strip --strip-unneeded $(MODULE_NAME).ko
+else
+ $(KERNEL_CROSS_COMPILE)strip --strip-unneeded $(OUT_STA_KO)
+endif
+endif
+
+##############################################################
+# Post processing
+##############################################################
+
+#= = = = = = = = = = = = = = = = = = = = = = = = = = = =
+# MT53XX start
+#- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ifeq ($(PLATFORM),MT53XX)
+ifneq ($(DRIVER_PARTIAL_BUILD), y)
+ifneq ($(ANDROID),true)
+ if [ -d $(OUTPUT_ROOT)/basic/bt_ko ]; then \
+ cp -f $(OUT_STA_KO) $(OUTPUT_ROOT)/basic/bt_ko/$(MODULE_NAME).ko; \
+ $(KERNEL_STRIP) $(STRIP_FLAG) $(OUTPUT_ROOT)/basic/bt_ko/$(MODULE_NAME).ko; \
+ fi
+else
+ if [ ! -d $(OUTPUT_ROOT)/basic/modules ]; then \
+ mkdir -p $(OUTPUT_ROOT)/basic/modules/; \
+ fi
+ if [ -d $(OUTPUT_ROOT)/basic/modules ]; then \
+ cp -f $(OUT_STA_KO) $(OUTPUT_ROOT)/basic/modules/$(MODULE_NAME).ko; \
+ fi
+endif # ifneq ($(ANDROID),true)
+
+
+endif # ifneq ($(DRIVER_PARTIAL_BUILD), y)
+endif # ifeq ($(PLATFORM),MT53XX)
+#- - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# MT53XX end
+#= = = = = = = = = = = = = = = = = = = = = = = = = = = =
+
+clean: driver_clean
+ifeq ($(PLATFORM),MT53XX)
+ifeq ($(S_PLATFORM),true)
+ if [ -d $(OUTPUT_ROOT)/basic/modules ]; then \
+ rm -f $(OUTPUT_ROOT)/basic/modules/mt7921_patch_e1_hdr.bin; \
+ rm -f $(OUTPUT_ROOT)/basic/modules/mt7921_patch_e2_hdr.bin; \
+ fi
+endif
+endif
+
+driver_clean:
+ifneq ($(ANDROID),true)
+ rm -rf $(THIRDPARTY_LIB_ROOT)/bt
+endif
+ if [ -d $(DRIVER_DIR) ] && [ -d $(LINUX_SRC) ]; then \
+ cd $(DRIVER_DIR) && make -C $(LINUX_SRC) M=$(DRIVER_DIR) MODULE_NAME=$(MODULE_NAME) clean; \
+ fi
+ if [ -e $(DRIVER_DIR)/$(MODULE_NAME).ko ]; then rm $(DRIVER_DIR)/$(MODULE_NAME).ko; fi;
+
+
+
+
+.PHONY: all clean driver driver_clean
+
diff --git a/proj/btmtk_proj_ce.c b/proj/btmtk_proj_ce.c
new file mode 100644
index 0000000..4752db7
--- /dev/null
+++ b/proj/btmtk_proj_ce.c
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#include "btmtk_main.h"
diff --git a/proj/btmtk_proj_sp.c b/proj/btmtk_proj_sp.c
new file mode 100644
index 0000000..c6719f0
--- /dev/null
+++ b/proj/btmtk_proj_sp.c
@@ -0,0 +1,534 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include "btmtk_main.h"
+#include "connv3.h"
+#include "conninfra.h"
+#include "connfem.h"
+#include "btmtk_proj_sp.h"
+#include "btmtk_uart_tty.h"
+#include <linux/platform_device.h>
+
+
+#define READ_PMIC_STATE_CMD_LEN 16
+#define READ_PMIC_STATE_EVENT_LEN 16
+
+#define DEFAULT_STATE_PINCTRL_NAME ("bt_combo_gpio_init")
+#define PRE_ON_PINCTRL_NAME ("bt_combo_gpio_pre_on")
+#define POWER_ON_PINCTRL_NAME ("bt_combo_gpio_on")
+#define RST_ON_PINCTRL_NAME ("bt_rst_on")
+#define RST_OFF_PINCTRL_NAME ("bt_rst_off")
+#define INIT_STATE_PINCTRL_NAME ("bt_combo_gpio_init")
+
+
+#if (USE_DEVICE_NODE == 1)
+static struct pinctrl *pinctrl_ptr;
+extern struct btmtk_dev *g_sbdev;
+
+static inline int btmtk_pinctrl_exec(const char *name)
+{
+ struct pinctrl_state *pinctrl;
+ int ret = -1;
+
+ BTMTK_INFO("%s start %s", __func__, name);
+ if (IS_ERR(pinctrl_ptr)) {
+ BTMTK_ERR("[ERR] %s: fail to get bt pinctrl", __func__);
+ return -1;
+ }
+
+ pinctrl = pinctrl_lookup_state(pinctrl_ptr, name);
+ if (!IS_ERR(pinctrl)) {
+ ret = pinctrl_select_state(pinctrl_ptr, pinctrl);
+ if (ret) {
+ BTMTK_ERR("%s: pinctrl %s fail [%d]", __func__, name, ret);
+ return -1;
+ }
+ } else {
+ BTMTK_ERR("%s: pinctrl %s lookup fail", __func__, name);
+ return -1;
+ }
+
+ return 0;
+}
+int btmtk_pre_power_on_handler(void)
+{
+ /*
+ * Set BT_RST PU/OUPUT
+ * Setup BT UART
+ */
+ int ret = 0;
+
+ btmtk_pinctrl_exec(RST_ON_PINCTRL_NAME);
+ ret = btmtk_pinctrl_exec(PRE_ON_PINCTRL_NAME);
+ msleep(100);
+ BTMTK_DBG("%s: wait 100ms", __func__);
+ return ret;
+}
+
+int btmtk_reset_pin_off(void)
+{
+ BTMTK_DBG("%s: start", __func__);
+ return btmtk_pinctrl_exec(RST_OFF_PINCTRL_NAME);
+}
+
+int btmtk_set_gpio_default(void)
+{
+ BTMTK_DBG("%s: start", __func__);
+ btmtk_pinctrl_exec(RST_OFF_PINCTRL_NAME);
+ msleep(10);
+ return btmtk_pinctrl_exec(DEFAULT_STATE_PINCTRL_NAME);
+}
+
+int btmtk_set_uart_auxFunc(void)
+{
+ BTMTK_DBG("%s: start", __func__);
+
+ return btmtk_pinctrl_exec(POWER_ON_PINCTRL_NAME);
+ //return btmtk_read_pmic_state(NULL);
+}
+
+static int btmtk_power_on_notify_handler(void)
+{
+ /* Execute BT power on then power off (if BT is off before this callback */
+ BTMTK_INFO("%s", __func__);
+ return 0;
+}
+
+static int btmtk_pre_chip_rst_handler(enum connv3_drv_type drv, char *reason)
+{
+ /* Ask FW to do coredump */
+ BTMTK_INFO("%s", __func__);
+ return 0;
+}
+
+static int btmtk_post_chip_rst_handler(void)
+{
+ BTMTK_INFO("%s", __func__);
+ btmtk_send_hw_err_to_host(NULL);
+ return 0;
+}
+
+struct connv3_whole_chip_rst_cb btmtk_whole_chip_rst_cb = {
+ .pre_whole_chip_rst = btmtk_pre_chip_rst_handler,
+ .post_whole_chip_rst = btmtk_post_chip_rst_handler,
+};
+
+struct connv3_power_on_cb btmtk_pwr_on_cb = {
+ .pre_power_on = btmtk_pre_power_on_handler,
+ .power_on_notify = btmtk_power_on_notify_handler,
+};
+
+/* connv3_sub_drv_ops_cb
+ *
+ * All callbacks needs by conninfra driver, 3 types of callback functions
+ * 1. power on
+ * 2. chip reset
+ * 3. pre-calibration
+ */
+#if 0 // can not build
+struct connv3_sub_drv_ops_cb btmtk_drv_cbs = {
+ .pwr_on_cb = btmtk_pwr_on_cb,
+ .rst_cb = btmtk_whole_chip_rst_cb,
+ //.pre_cal_cb = NULL,
+};
+#endif
+
+int btmtk_read_pmic_state(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u8 read_pmic_state_cmd[READ_PMIC_STATE_CMD_LEN] = { 0x00 };
+ u8 read_pmic_state_event[READ_PMIC_STATE_EVENT_LEN] = { 0x00 };
+
+
+ BTMTK_INFO("%s enter", __func__);
+ ret = btmtk_main_send_cmd(bdev, read_pmic_state_cmd, READ_PMIC_STATE_CMD_LEN,
+ read_pmic_state_event, READ_PMIC_STATE_EVENT_LEN, 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ return ret;
+}
+
+int btmtk_send_connfem_cmd(struct btmtk_dev *bdev)
+{
+ struct connfem_epaelna_fem_info fem_info;
+ struct connfem_epaelna_flags_common common_flag;
+ struct connfem_epaelna_pin_info pin_info;
+ struct connfem_epaelna_flags_bt bt_flag;
+ uint32_t ret = 0;
+ uint8_t *cmd = NULL;
+ uint8_t cmd_header[] = {0x01, 0x6F, 0xFC, 0x42, 0x01, 0x55, 0x3E, 0x00,
+ 0x01, 0x04, 0x03, 0x33, 0x00, 0x10};
+ uint8_t event[] = {0x04, 0xE4, 0x06, 0x02, 0x55, 0x02, 0x00, 0x00, 0x01};
+ uint32_t cmd_len = 0, i = 0, offset = 0;
+ const uint32_t pin_struct_size = sizeof(struct connfem_epaelna_pin);
+
+ BTMTK_INFO("%s", __func__);
+
+ /* Get data from connfem_api */
+ connfem_epaelna_get_fem_info(&fem_info);
+ connfem_epaelna_get_pin_info(&pin_info);
+ connfem_epaelna_get_flags(CONNFEM_SUBSYS_NONE, &common_flag);
+ connfem_epaelna_get_flags(CONNFEM_SUBSYS_BT, &bt_flag);
+
+ if (fem_info.part[CONNFEM_PORT_BT].vid == 0 &&
+ fem_info.part[CONNFEM_PORT_BT].pid == 0) {
+ BTMTK_INFO("CONNFEM BTvid/pid == 0, ignore");
+ return 0;
+ }
+
+ /*
+ * command and event example
+ * 0 1 2 3 4 5 6 7 8 9 A B C D
+ * 01 6F FC length 01 55 LL LL 01 XX XX XX XX NN YYYYYY .. YYYYYY AA BB BB CC DD DD DD DD
+ * lengthL : LL + 4
+ * LLLL : length = 1 + 4 + 1 + 3*num + 3 (only 1 byte length valid,
+ * value 251 should be maxium)
+ * XXXXXXXX : 4 byte, efem ID
+ * NN : 1 byte, total efem number
+ * YYYYYY: 3 byte * number, u1AntSelNo, u1FemPin, u1Polarity;
+ * AA : bt flag
+ * BBBB : 2.4G part = VID + PID
+ * CC : 1 byte Rx Mode info
+ * DDDDDDDD: 4 bytes SPDT info
+ *
+ * RX: 04 E4 06 02 55 02 00 01 SS (SS : status)
+ */
+ cmd_len = sizeof(cmd_header) + pin_info.count * pin_struct_size + 8;
+ cmd = vmalloc(cmd_len);
+ if (!cmd) {
+ BTMTK_ERR("unable to allocate confem command");
+ return -1;
+ }
+
+ memcpy(cmd, cmd_header, sizeof(cmd_header));
+
+ /* assign WMT over HCI command length */
+ cmd[3] = cmd_len - 4;
+
+ /* assign payload length */
+ cmd[6] = cmd_len - 8;
+
+ /* assign femid */
+ memcpy(&cmd[9], &fem_info.id, sizeof(fem_info.id));
+ offset = sizeof(cmd_header);
+
+ /* assign pin count */
+ cmd[offset-1] = pin_info.count;
+
+ /* assign pin mapping info */
+ for (i = 0; i < pin_info.count; i++) {
+ memcpy(&cmd[offset], &pin_info.pin[i], pin_struct_size);
+ offset += pin_struct_size;
+ }
+
+ /* config priority: epa_elna > elna > epa > bypass */
+ cmd[offset++] = (bt_flag.epa_elna) ? 3 :
+ (bt_flag.epa) ? 2 :
+ (bt_flag.elna) ? 1 : 0;
+
+ cmd[offset++] = fem_info.part[CONNFEM_PORT_BT].vid;
+ cmd[offset++] = fem_info.part[CONNFEM_PORT_BT].pid;
+
+ cmd[offset++] = common_flag.rxmode;
+ cmd[offset++] = common_flag.fe_ant_cnt;
+ cmd[offset++] = common_flag.fe_main_bt_share_lp2g;
+ cmd[offset++] = common_flag.fe_conn_spdt;
+ cmd[offset++] = common_flag.fe_reserved;
+
+ BTMTK_INFO_RAW(cmd, offset, "%s: Send: ", __func__);
+
+ ret = btmtk_main_send_cmd(bdev, cmd, cmd_len,
+ event, sizeof(event), 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ if (ret < 0)
+ BTMTK_ERR("%s: failed(%d)", __func__, ret);
+
+ vfree(cmd);
+ return 0;
+}
+
+int btmtk_set_pcm_pin_mux(void)
+{
+ return 0;
+}
+
+int btmtk_connv3_sub_drv_init(struct btmtk_dev *bdev)
+{
+ struct btmtk_uart_dev *cif_dev = NULL;
+ struct tty_struct *tty = NULL;
+ struct connv3_sub_drv_ops_cb btmtk_drv_cbs;
+ int ret;
+
+ btmtk_drv_cbs.pwr_on_cb = btmtk_pwr_on_cb;
+ btmtk_drv_cbs.rst_cb = btmtk_whole_chip_rst_cb;
+ BTMTK_INFO("%s start", __func__);
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ if (!cif_dev) {
+ BTMTK_ERR("[ERR] cif_dev is NULL");
+ return -1;
+ }
+
+ tty = cif_dev->tty;
+ if (!tty) {
+ BTMTK_ERR("[ERR] tty is NULL");
+ return -1;
+ }
+
+ tty->dev->of_node = of_find_compatible_node(NULL, NULL, "mediatek,bt");
+ if (!tty->dev->of_node)
+ BTMTK_ERR("[ERR] %s: mediatek,bt of_node not found", __func__);
+
+ ret = of_property_read_u32(tty->dev->of_node, "baudrate", &cif_dev->baudrate);
+ if (ret < 0)
+ BTMTK_ERR("[ERR] %s: mediatek,bt baudrate ret[%d]", __func__, ret);
+
+ ret = of_property_read_u32(tty->dev->of_node, "hub-en", &cif_dev->hub_en);
+ if (ret < 0)
+ BTMTK_ERR("[ERR] %s: mediatek,bt hub-en ret[%d]", __func__, ret);
+
+ ret = of_property_read_u32(tty->dev->of_node, "sleep-en", &cif_dev->sleep_en);
+ if(ret < 0)
+ BTMTK_ERR("[ERR] %s: mediatek,bt sleep-en ret[%d]", __func__, ret);
+
+ /* temp: for disable sleep */
+ cif_dev->sleep_en = 0;
+
+ pinctrl_ptr = devm_pinctrl_get(tty->dev);
+ if (IS_ERR(pinctrl_ptr)) {
+ BTMTK_ERR("[ERR] %s: fail to get bt pinctrl", __func__);
+ return -1;
+ }
+ //btmtk_pinctrl_exec(INIT_STATE_PINCTRL_NAME);
+ connv3_sub_drv_ops_register(CONNV3_DRV_TYPE_BT, &btmtk_drv_cbs);
+ BTMTK_INFO("%s end, baudrate[%d] hub_en[%d] sleep_en[%d]", __func__, cif_dev->baudrate, cif_dev->hub_en, cif_dev->sleep_en);
+ return 0;
+}
+
+
+int btmtk_connv3_sub_drv_deinit(void)
+{
+ return connv3_sub_drv_ops_unregister(CONNV3_DRV_TYPE_BT);
+}
+
+/*
+ *******************************************************************************
+ * bt power throttling feature
+ *******************************************************************************
+ */
+static inline bool btmtk_pwrctrl_support(void)
+{
+ return TRUE;
+}
+
+static void btmtk_send_set_tx_power_cmd(struct btmtk_dev *bdev)
+{
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ struct btmtk_dypwr_st *dy_pwr = &cif_dev->dy_pwr;
+ uint8_t cmd_set[6] = { 0x01, 0x2D, 0xFC, 0x02, 0x02, 0x00 };
+ uint8_t evt_set[] = { 0x04, 0x0E, 0x06, 0x01, 0x2D, 0xFC };
+
+ cmd_set[5] = dy_pwr->set_val;
+ btmtk_main_send_cmd(bdev, cmd_set, sizeof(cmd_set),
+ evt_set, sizeof(evt_set), 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ if (bdev->io_buf[6] != HCI_EVT_CC_STATUS_SUCCESS)
+ BTMTK_ERR("%s: status error[0x%02x]!", __func__, bdev->io_buf[6]);
+ else {
+ dy_pwr->fw_sel_dbm = bdev->io_buf[8];
+ BTMTK_INFO("%s: fw_sel_dbm[%d]", __func__, dy_pwr->fw_sel_dbm);
+ }
+
+ if (dy_pwr->cb != NULL)
+ dy_pwr->cb(dy_pwr->buf, dy_pwr->len);
+}
+
+void btmtk_async_trx_work(struct work_struct *work)
+{
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, async_trx_work);
+
+ btmtk_send_set_tx_power_cmd(bdev);
+}
+
+int btmtk_query_tx_power(struct btmtk_dev *bdev, BT_RX_EVT_HANDLER_CB cb)
+{
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ uint8_t cmd_query[] = { 0x01, 0x2D, 0xFC, 0x01, 0x01 };
+ uint8_t evt_query[] = { 0x04, 0x0E, 0x08, 0x01, 0x2D, 0xFC };
+
+ if (!btmtk_pwrctrl_support())
+ return 0;
+
+ BTMTK_INFO("%s: lp_cur_lv[%d], dy_max_dbm[%d], dy_min_dbm[%d], lp_bdy_dbm[%d], fw_sel_dbm[%d]",
+ __func__,
+ cif_dev->dy_pwr.lp_cur_lv,
+ cif_dev->dy_pwr.dy_max_dbm,
+ cif_dev->dy_pwr.dy_min_dbm,
+ cif_dev->dy_pwr.lp_bdy_dbm,
+ cif_dev->dy_pwr.fw_sel_dbm);
+
+ /*
+ * Query
+ * Cmd: 01 2D FC 01 01
+ * Evt: 04 0E 08 01 2D FC SS 01 XX YY ZZ
+ * SS: Status
+ * XX: Dynamic range Max dBm
+ * YY: Dynamic range Min dBm
+ * ZZ: Low power region boundary dBm
+ */
+ btmtk_main_send_cmd(bdev, cmd_query, sizeof(cmd_query),
+ evt_query, sizeof(evt_query), 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ if (bdev->io_buf[6] != HCI_EVT_CC_STATUS_SUCCESS)
+ BTMTK_ERR("%s: status error[0x%02x]!", __func__, bdev->io_buf[6]);
+ else {
+ cif_dev->dy_pwr.dy_max_dbm = bdev->io_buf[8];
+ cif_dev->dy_pwr.dy_min_dbm = bdev->io_buf[9];
+ cif_dev->dy_pwr.lp_bdy_dbm = bdev->io_buf[10];
+ BTMTK_INFO("%s: dy_max_dbm[%d], dy_min_dbm[%d], lp_bdy_dbm[%d]",
+ __func__,
+ cif_dev->dy_pwr.dy_max_dbm,
+ cif_dev->dy_pwr.dy_min_dbm,
+ cif_dev->dy_pwr.lp_bdy_dbm);
+ }
+
+ if (cb)
+ cb(cif_dev->dy_pwr.buf, cif_dev->dy_pwr.len);
+
+ return 0;
+}
+
+int btmtk_set_tx_power(struct btmtk_dev *bdev, int8_t req_val, BT_RX_EVT_HANDLER_CB cb)
+{
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ struct btmtk_dypwr_st *dy_pwr = &cif_dev->dy_pwr;
+ bool isblocking = (cb) ? TRUE : FALSE;
+
+ /*
+ * Set
+ * Cmd: 01 2D FC 02 02 RR
+ * Evt: 04 0E 06 01 2D FC SS 02 XX
+ * RR: Requested TX power upper limitation dBm
+ * SS: Status
+ * XX: Selected TX power upper limitation dBm
+ */
+
+ /* do not send if not support */
+ if (dy_pwr->dy_max_dbm <= dy_pwr->dy_min_dbm) {
+ BTMTK_INFO("%s: invalid dbm range, skip set cmd", __func__);
+ return 1;
+ }
+
+ /* value select flow */
+ if (req_val >= dy_pwr->dy_min_dbm) {
+ dy_pwr->set_val = req_val;
+ /* check max limitation */
+ if (dy_pwr->set_val > dy_pwr->dy_max_dbm)
+ dy_pwr->set_val = dy_pwr->dy_max_dbm;
+ /* power throttling limitation */
+ if (dy_pwr->lp_cur_lv >= CONN_PWR_THR_LV_4) { //TODO_PWRCTRL
+ dy_pwr->set_val = dy_pwr->lp_bdy_dbm;
+ /* lp_bdy_dbm may be larger than dy_max_dbm, check again */
+ if (dy_pwr->set_val > dy_pwr->dy_max_dbm)
+ dy_pwr->set_val = dy_pwr->dy_max_dbm;
+ }
+ } else {
+ BTMTK_INFO("%s: invalid dbm value, skip set cmd", __func__);
+ return 1;
+ }
+
+ BTMTK_INFO("%s: set_val[%d]", __func__, dy_pwr->set_val);
+
+ dy_pwr->cb = cb;
+ if (isblocking)
+ btmtk_send_set_tx_power_cmd(bdev);
+ else
+ /* Don't block caller thread for set operation */
+ schedule_work(&bdev->async_trx_work);
+ return 0;
+}
+
+
+int btmtk_pwrctrl_level_change_cb(enum conn_pwr_event_type type, void *data)
+{
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)g_sbdev->cif_dev;
+ int8_t set_val = cif_dev->dy_pwr.dy_max_dbm;
+
+ BTMTK_INFO("%s", __func__);
+ switch (type) {
+ case CONN_PWR_EVENT_LEVEL:
+ cif_dev->dy_pwr.lp_cur_lv = *((int *) data);
+ BTMTK_INFO("%s: lp_cur_bat_lv = %d", __func__, cif_dev->dy_pwr.lp_cur_lv);
+ btmtk_set_tx_power(g_sbdev, set_val, NULL);
+ break;
+ case CONN_PWR_EVENT_MAX_TEMP:
+ BTMTK_ERR("Unsupport now");
+ break;
+ default:
+ BTMTK_ERR("Uknown type for power throttling callback");
+ break;
+ }
+
+ return 0;
+}
+
+int btmtk_pwrctrl_pre_on(struct btmtk_dev *bdev)
+{
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ if (!btmtk_pwrctrl_support())
+ return 0;
+
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ return -1;
+ }
+
+ cif_dev = bdev->cif_dev;
+ memset(&cif_dev->dy_pwr, 0x00, sizeof(cif_dev->dy_pwr));
+ cif_dev->dy_pwr.lp_cur_lv = CONN_PWR_THR_LV_0;
+ conn_pwr_drv_pre_on(CONN_PWR_DRV_BT, &cif_dev->dy_pwr.lp_cur_lv);
+ BTMTK_INFO("%s: lp_cur_bat_lv = %d", __func__, cif_dev->dy_pwr.lp_cur_lv);
+ return 0;
+}
+
+void btmtk_pwrctrl_post_off(void)
+{
+ if (!btmtk_pwrctrl_support())
+ return;
+
+ conn_pwr_drv_post_off(CONN_PWR_DRV_BT);
+}
+
+void btmtk_pwrctrl_register_evt(void)
+{
+ if (!btmtk_pwrctrl_support())
+ return;
+
+ BTMTK_INFO("%s", __func__);
+ /* Register callbacks for power throttling feature */
+ conn_pwr_register_event_cb(CONN_PWR_DRV_BT, (CONN_PWR_EVENT_CB)btmtk_pwrctrl_level_change_cb);
+}
+
+#endif // (USE_DEVICE_NODE == 1)
+
diff --git a/proj/btmtk_proj_sp_debug.c b/proj/btmtk_proj_sp_debug.c
new file mode 100644
index 0000000..e77f9a3
--- /dev/null
+++ b/proj/btmtk_proj_sp_debug.c
@@ -0,0 +1,444 @@
+/**
+ * Copyright (c) 2018 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+
+#include <btmtk_main.h>
+#include "connv3.h"
+
+#define BT_CR_DUMP_BUF_SIZE (1024)
+#define BT_RHW_MAX_ERR_COUNT (3)
+#define DBG_TAG "[btmtk_dbg_sop]"
+
+struct bt_dump_cr_buffer {
+ uint8_t buffer[BT_CR_DUMP_BUF_SIZE];
+ uint32_t cr_count;
+ uint32_t count;
+ uint8_t *pos;
+ uint8_t *end;
+};
+
+struct bt_dump_cr_buffer g_btmtk_cr_dump;
+struct btmtk_dev *g_dump_bdev;
+uint32_t g_rhw_fail;
+
+static inline void BT_DUMP_CR_BUFFER_RESET(void)
+{
+ memset(g_btmtk_cr_dump.buffer, 0, BT_CR_DUMP_BUF_SIZE);
+ g_btmtk_cr_dump.pos = &g_btmtk_cr_dump.buffer[0];
+ g_btmtk_cr_dump.end = g_btmtk_cr_dump.pos + BT_CR_DUMP_BUF_SIZE - 1;
+}
+
+static inline void BT_DUMP_CR_INIT(uint32_t cr_count)
+{
+ BT_DUMP_CR_BUFFER_RESET();
+ g_btmtk_cr_dump.count = 0;
+ g_btmtk_cr_dump.cr_count = cr_count;
+}
+
+static inline int BT_DUMP_CR_PRINT(uint32_t value)
+{
+ uint32_t ret = 0;
+
+ ret = snprintf(g_btmtk_cr_dump.pos,
+ (g_btmtk_cr_dump.end - g_btmtk_cr_dump.pos + 1),
+ "%08x ", value);
+ if (ret < 0 || ret >= (g_btmtk_cr_dump.end - g_btmtk_cr_dump.pos + 1)) {
+ BTMTK_ERR("%s %s: error in sprintf while dumping cr", DBG_TAG, __func__);
+ if (g_btmtk_cr_dump.count)
+ BTMTK_WARN("%s %s",DBG_TAG, g_btmtk_cr_dump.buffer);
+ return -1;
+ }
+
+ g_btmtk_cr_dump.pos += ret;
+ g_btmtk_cr_dump.count++;
+
+ if ((g_btmtk_cr_dump.count & 0xF) == 0 ||
+ g_btmtk_cr_dump.count == g_btmtk_cr_dump.cr_count) {
+ BTMTK_WARN("%s %s",DBG_TAG, g_btmtk_cr_dump.buffer);
+ BT_DUMP_CR_BUFFER_RESET();
+ }
+
+ return 0;
+}
+
+int RHW_WRITE(uint32_t addr, uint32_t val)
+{
+ int ret = -1;
+ /* ex: write dummy CR 0x022121cc = 0x44332222 */
+ u8 cmd[RHW_PKT_LEN] = {0x40, 0x00, 0x00, 0x08, 0x00,
+ 0xCC, 0x21, 0x21, 0x02,
+ 0x22, 0x22, 0x33, 0x44};
+ u8 evt[RHW_PKT_LEN] = {0x40, 0x00, 0x00, 0x08, 0x00,
+ 0xCC, 0x21, 0x21, 0x02,
+ 0x22, 0x22, 0x33, 0x44};
+
+ if (g_rhw_fail >= BT_RHW_MAX_ERR_COUNT) {
+ BTMTK_WARN("%s skip, g_rhw_fail[%d]", __func__, g_rhw_fail);
+ return ret;
+ }
+
+ BTMTK_DBG("%s: write addr[%x], value[0x%08x]", __func__, addr, val);
+ memcpy(&cmd[RHW_ADDR_OFFSET_CMD], &addr, RHW_ADDR_LEN);
+ memcpy(&cmd[RHW_VAL_OFFSET_CMD], &val, RHW_VAL_LEN);
+
+ memcpy(&evt[RHW_ADDR_OFFSET_CMD], &addr, RHW_ADDR_LEN);
+
+ ret = btmtk_main_send_cmd(g_dump_bdev, cmd, RHW_PKT_LEN, evt, RHW_PKT_COMP_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0)
+ BTMTK_ERR("%s failed, g_rhw_fail[%d]", __func__, ++g_rhw_fail);
+
+ return ret ;
+
+}
+
+int RHW_READ(uint32_t addr, uint32_t *val)
+{
+ int ret = -1;
+ /* ex: read dummy CR 0x022121cc */
+ u8 cmd[RHW_PKT_LEN] = {0x41, 0x00, 0x00, 0x08, 0x00,
+ 0xCC, 0x21, 0x21, 0x02,
+ 0x00, 0x00, 0x00, 0x00};
+
+ u8 evt[RHW_PKT_LEN] = {0x41, 0x00, 0x00, 0x08, 0x00,
+ 0xCC, 0x21, 0x21, 0x02,
+ 0x00, 0x00, 0x00, 0x00};
+
+ if (g_rhw_fail >= BT_RHW_MAX_ERR_COUNT) {
+ *val = 0xdeaddead;
+ BTMTK_WARN("%s skip, g_rhw_fail[%d]", __func__, g_rhw_fail);
+ return ret;
+ }
+ memcpy(&cmd[RHW_ADDR_OFFSET_CMD], &addr, sizeof(addr));
+ memcpy(&evt[RHW_ADDR_OFFSET_CMD], &addr, sizeof(addr));
+
+ ret = btmtk_main_send_cmd(g_dump_bdev, cmd, RHW_PKT_LEN, evt, RHW_PKT_COMP_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ if (ret >= 0) {
+ memcpy(val, g_dump_bdev->io_buf + RHW_PKT_COMP_LEN, sizeof(u32));
+ *val = le32_to_cpu(*val);
+ } else {
+ *val = 0xdeaddead;
+ BTMTK_ERR("%s failed, g_rhw_fail[%d]", __func__, ++g_rhw_fail);
+ }
+ BTMTK_DBG("%s: addr[%x], val[0x%08x]", __func__, addr, *val);
+
+ return ret;
+
+}
+
+static inline void btmtk_dump_bg_mcu_core(void)
+{
+ uint32_t i = 0, org_value, value, cr_count = 0x26 + 4;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG MCU core registers] - mcu_flg1, mcu_flg2 count[%d]", DBG_TAG, cr_count);
+ RHW_WRITE(0x81025020, 0x00000201);
+ RHW_READ(0x80000A00, &org_value);
+
+ /* mcu flag1 */
+ for (i = 0; i < 0x26; i++) {
+ value = (org_value & 0xC0FFFFFF) | (i << 24);
+
+ RHW_WRITE(0x80000A00, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ value = 0x0403 | (i << 16);
+
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_dsp_debug_flags(void)
+{
+ uint32_t i = 0, value, cr_count = 0x16 + 1;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG DSP debug flags] - mcu_flg3, mcu_flg4 count[%d]", DBG_TAG, cr_count);
+
+ RHW_WRITE(0x81025020, 0x00000605);
+ RHW_READ(0x8000040C, &value);
+ BT_DUMP_CR_PRINT(value);
+
+ for (i = 0; i <= 0x16; i++) {
+ value = 0x0807 | (i << 16);
+
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_mcusys_clk_gals_debug_flags(void)
+{
+ uint32_t i = 0, value, cr_count = 4 + 8;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG MCUSYS CLK and GALS debug flags] - mcu_flg5, mcu_flg6 count[%d]", DBG_TAG, cr_count);
+
+ /* mcu_flag5 */
+ for (i = 0; i < 4; i++) {
+ value = 0x0A09 | (i << 16);
+
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flag6 */
+ RHW_WRITE(0x81025020, 0x00000C0B);
+ for (i = 0; i < 8; i++) {
+ value = i << 16;
+
+ RHW_WRITE(0x80000408, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_mcu_pc_lr(void)
+{
+ uint32_t i = 0, value, cr_count = 0x55;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG MCU PC/LR log] - mcu_flg7[84:168] cpu_dbg_pc_log0 ~ conn_debug_port84 count[%d]"
+ , DBG_TAG, cr_count);
+
+ /* mcu_flag7 */
+ RHW_WRITE(0x81025020, 0x00000E0D);
+ for (i = 0; i <= 0x54; i++) {
+ RHW_WRITE(0x80000400, i);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_dsp_pc_lr(void)
+{
+ uint32_t i = 0, value, cr_count = 0x44;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG DSP PC/LR log] - mcu_flg7[169:236] cpu1_dbg_pc_log0 ~ cpu1_lr count[%d]"
+ , DBG_TAG, cr_count);
+
+ /* mcu_flag7 */
+ RHW_WRITE(0x81025020, 0x00000E0D);
+ for (i = 0x55; i <= 0x98; i++) {
+ RHW_WRITE(0x80000400, i);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_peri_debug_flags(void)
+{
+ uint32_t i = 0, value, cr_count = 4 + 6;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG PERI debug flags] - mcu_flg11, mcu_flg12 count[%d]", DBG_TAG, cr_count);
+
+ /* mcu_flag11 */
+ for (i = 0; i < 4; i++) {
+ value = 0x00001615 | (i << 16);
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flag12 */
+ for (i = 0; i < 6; i++) {
+ value = 0x00001817 | (i << 16);
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_bus_debug_flags(void)
+{
+ uint32_t i = 0, value, cr_count = 10 + 15 + 17;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG BUS debug flags] - mcu_flg13, mcu_flg14, mcu_flg16 count[%d]", DBG_TAG, cr_count);
+
+ /* mcu_flag13 */
+ RHW_WRITE(0x81025020, 0x00001A19);
+
+ for (i = 0; i <= 9; i++) {
+ value = (i << 12);
+ RHW_WRITE(0x80000408, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flg14 */
+ for (i = 0; i < 0xF; i++) {
+ value = 0x00001C1B | (i << 16);
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flg16 */
+ for (i = 0; i <= 0x10; i++) {
+ value = 0x0000201F | (i << 16);
+ RHW_WRITE(0x81025020, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+}
+
+static inline void btmtk_dump_dma_uart_debug_flags(void)
+{
+ uint32_t i = 0, value, cr_count = 8 + 1 + 14 + 17;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG DMA and UART debug flags] - mcu_flg19, mcu_flg20, mcu_flg23, mcu_flg24 count[%d]"
+ , DBG_TAG, cr_count);
+
+ /* mcu_flag19 */
+ RHW_WRITE(0x81025020, 0x00002625);
+ for (i = 0; i <= 7; i++) {
+ value = (i << 8);
+ RHW_WRITE(0x80000408, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flg20 */
+ RHW_WRITE(0x81025020, 0x00002827);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+
+ /* mcu_flag23 */
+ RHW_WRITE(0x81025020, 0x00002E2D);
+ for (i = 0; i <= 0x0D; i++) {
+ value = (i << 8);
+ RHW_WRITE(0x80000404, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ /* mcu_flag24 */
+ RHW_WRITE(0x81025020, 0x0000302F);
+ for (i = 0; i <= 0x0F; i++) {
+ value = (i << 16);
+ RHW_WRITE(0x80000404, value);
+ RHW_READ(0x8000040C, &value);
+ if (BT_DUMP_CR_PRINT(value))
+ return;
+ }
+
+ RHW_WRITE(0x80000404, 0x001E0000);
+ BT_DUMP_CR_PRINT(value);
+}
+
+static inline void btmtk_dump_cryto_debug_flags(void)
+{
+ uint32_t value, cr_count = 1 + 1;
+
+ BT_DUMP_CR_INIT(cr_count);
+ BTMTK_INFO("%s [BG CRYPTO debug flags] - mcu_flg21, mcu_flg22 (optional) count[%d]", DBG_TAG, cr_count);
+
+ /* mcu_flag21 */
+ RHW_WRITE(0x81025020, 0x00002827);
+ RHW_READ(0x8000040C, &value);
+ BT_DUMP_CR_PRINT(value);
+
+ /* mcu_flg22 */
+ RHW_WRITE(0x81025020, 0x00002A29);
+ RHW_READ(0x8000040C, &value);
+ BT_DUMP_CR_PRINT(value);
+}
+
+/* connv3_conninfra_bus_dump(enum connv3_drv_type drv_type, struct connv3_cr_cb *cb, void * priv_data)
+ *
+ * drv_type: driver type
+ * cb: callback function provided by subsys to read/write CR
+ * addr is fw view
+ */
+
+static int btmtk_connv3_cr_read_cb(void* priv_data, unsigned int addr, unsigned int *value)
+{
+ return RHW_READ(addr, value);
+}
+
+static int btmtk_connv3_cr_write_cb(void* priv_data, unsigned int addr, unsigned int value)
+{
+ return RHW_WRITE(addr, value);
+}
+
+static int btmtk_connv3_cr_write_mask_cb(void* priv_data, unsigned int addr, unsigned int mask, unsigned int value)
+{
+ int ret = 0;
+ uint32_t org_value;
+ ret = RHW_READ(addr, &org_value);
+ if (ret < 0) {
+ BTMTK_ERR("%s: read [%x] err", __func__, addr);
+ return ret;
+ }
+
+ org_value = (org_value & ~mask ) | value;
+
+ return RHW_WRITE(addr, org_value);
+}
+
+struct connv3_cr_cb btmtk_connv3_cr_cb = {
+ .read = btmtk_connv3_cr_read_cb,
+ .write = btmtk_connv3_cr_write_cb,
+ .write_mask = btmtk_connv3_cr_write_mask_cb
+};
+
+void btmtk_uart_sp_dump_debug_sop(struct btmtk_dev *bdev)
+{
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+ BTMTK_INFO("%s: start", __func__);
+ g_dump_bdev = bdev;
+ g_rhw_fail = 0;
+ btmtk_dump_bg_mcu_core();
+ btmtk_dump_dsp_debug_flags();
+ btmtk_dump_mcusys_clk_gals_debug_flags();
+ btmtk_dump_mcu_pc_lr();
+ btmtk_dump_dsp_pc_lr();
+ btmtk_dump_peri_debug_flags();
+ btmtk_dump_bus_debug_flags();
+ btmtk_dump_dma_uart_debug_flags();
+ btmtk_dump_cryto_debug_flags();
+ BTMTK_INFO("%s: connv3_conninfra_bus_dump start", __func__);
+ connv3_conninfra_bus_dump(CONNV3_DRV_TYPE_BT, &btmtk_connv3_cr_cb, NULL);
+ BTMTK_INFO("%s: end, g_rhw_fail[%d] ", __func__, g_rhw_fail);
+}
+
diff --git a/proj/include/btmtk_proj_sp.h b/proj/include/btmtk_proj_sp.h
new file mode 100644
index 0000000..4b231ee
--- /dev/null
+++ b/proj/include/btmtk_proj_sp.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#ifndef _BTMTK_PROJ_SP_H_
+#define _BTMTK_PROJ_SP_H_
+
+#if (USE_DEVICE_NODE == 1)
+#include "conn_power_throttling.h"
+
+#define HCI_EVT_COMPLETE_EVT 0x0E
+#define HCI_EVT_STATUS_EVT 0x0F
+#define HCI_EVT_CC_STATUS_SUCCESS 0x00
+#define HCI_CMD_DY_ADJ_PWR_QUERY 0x01
+#define HCI_CMD_DY_ADJ_PWR_SET 0x02
+
+typedef int (*BT_RX_EVT_HANDLER_CB) (uint8_t *buf, int len);
+
+struct btmtk_dypwr_st {
+ /* Power Throttling Feature */
+ uint8_t buf[16];
+ uint8_t len;
+ int8_t set_val;
+ int8_t dy_max_dbm;
+ int8_t dy_min_dbm;
+ int8_t lp_bdy_dbm;
+ int8_t fw_sel_dbm;
+ BT_RX_EVT_HANDLER_CB cb;
+ enum conn_pwr_low_battery_level lp_cur_lv;
+};
+
+void btmtk_async_trx_work(struct work_struct *work);
+int btmtk_pwrctrl_pre_on(struct btmtk_dev *bdev);
+void btmtk_pwrctrl_post_off(void);
+void btmtk_pwrctrl_register_evt(void);
+int btmtk_query_tx_power(struct btmtk_dev *bdev, BT_RX_EVT_HANDLER_CB cb);
+int btmtk_set_tx_power(struct btmtk_dev *bdev, int8_t req_val, BT_RX_EVT_HANDLER_CB cb);
+
+
+int btmtk_read_pmic_state(struct btmtk_dev *bdev);
+
+int btmtk_set_pcm_pin_mux(void);
+
+int btmtk_set_gpio_default(void);
+int btmtk_pre_power_on_handler(void);
+int btmtk_set_uart_auxFunc(void);
+
+int btmtk_connv3_sub_drv_init(struct btmtk_dev *bdev);
+//int btmtk_connv3_sub_drv_init(struct platform_device *pdev);
+
+int btmtk_connv3_sub_drv_deinit(void);
+
+/* Debug sop api */
+void btmtk_uart_sp_dump_debug_sop(struct btmtk_dev *bdev);
+#endif // (USE_DEVICE_NODE == 1)
+#endif
diff --git a/release_note b/release_note
new file mode 100644
index 0000000..fb970b0
--- /dev/null
+++ b/release_note
@@ -0,0 +1,27 @@
+Checking list:
+ 1. BT/WIFI FW/Driver MUST be updated at the same time. Please notify wifi owner for updating
+ 2. bt.cfg and woble_setting.bin should updated according to the each project. Sync from SQC release first and then updated are recommended
+ 3. If bin mode is enabled for the project, BT_ADDR/tx_power will be set by EEPROM.bin
+ 4. wifi.cfg will enable bin mode or not, and set coex mode(hybrid, legacy, TDD). Please confirm with wifi owner
+
+20220126 CL: 5395583 support debug sop sdio/USB cr for script file
+ userload disable.
+
+20220117 CL: 5516615 Fix Thread modeling issue
+ don't show full MAC address
+
+20220110 CL: 5489513 Build pass with Linux K4.19 and Android K5.10
+ Add MACRO: LINUX_OS&ANDROID_OS. Please define it for platform
+
+20211202 CL: 5332794 Add audio setting from bt.cfg.support to enable/disable audio setting cmd form bt.cfg.
+ Need update with bt.cfg.
+
+20211208 CL: 5340131 Picus save one more cmd opcode, filter all the events
+
+20211210 CL: 5359047 Add -Werro, all warning will be dealed as error
+
+20211217 CL: 5400714 1.add # for comment in bt.cfg
+ 2.default to enable audio setting if no SUPPORT_BT_AUDIO_SETTING in bt.cfg
+ 3.support change audio pinmux in bt.cfg(mt8188 pinmux will different)
+ 4.add release note
+ 5.fix get_hci_reset will not be set 1 and can't set audio setting.
diff --git a/sdio/btmtksdio.c b/sdio/btmtksdio.c
new file mode 100644
index 0000000..8c86a1b
--- /dev/null
+++ b/sdio/btmtksdio.c
@@ -0,0 +1,2613 @@
+/*
+ *
+ * Generic Bluetooth USB driver
+ *
+ * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/version.h>
+#if (KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE)
+#include <linux/sched.h>
+#else
+#include <uapi/linux/sched/types.h>
+#endif
+
+#include "btmtk_sdio.h"
+
+static char event_need_compare[EVENT_COMPARE_SIZE] = {0};
+static char event_need_compare_len;
+static char event_compare_status;
+
+static DEFINE_MUTEX(btmtk_sdio_own_mutex);
+#define SDIO_OWN_MUTEX_LOCK() mutex_lock(&btmtk_sdio_own_mutex)
+#define SDIO_OWN_MUTEX_UNLOCK() mutex_unlock(&btmtk_sdio_own_mutex)
+
+
+static DEFINE_MUTEX(btmtk_sdio_ops_mutex);
+#define SDIO_OPS_MUTEX_LOCK() mutex_lock(&btmtk_sdio_ops_mutex)
+#define SDIO_OPS_MUTEX_UNLOCK() mutex_unlock(&btmtk_sdio_ops_mutex)
+
+static DEFINE_MUTEX(btmtk_sdio_debug_mutex);
+#define SDIO_DEBUG_MUTEX_LOCK() mutex_lock(&btmtk_sdio_debug_mutex)
+#define SDIO_DEBUG_MUTEX_UNLOCK() mutex_unlock(&btmtk_sdio_debug_mutex)
+
+static int btmtk_sdio_readl(u32 offset, u32 *val, struct sdio_func *func);
+static int btmtk_sdio_writel(u32 offset, u32 val, struct sdio_func *func);
+
+#define DUMP_FW_PC(cif_dev) \
+do { \
+ u32 __value = 0; \
+ btmtk_sdio_read_bt_mcu_pc(&__value); \
+ BTMTK_INFO("%s, BT mcu pc: 0x%08X", __func__, __value); \
+ btmtk_sdio_read_conn_infra_pc(&__value); \
+ BTMTK_INFO("%s, power status: 0x%08X", __func__, __value); \
+} while (0)
+
+static struct btmtk_sdio_dev g_sdio_dev;
+
+static const struct sdio_device_id btmtk_sdio_tabls[] = {
+ /* Mediatek MT7961 Bluetooth device */
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x7961)
+ /*,
+ *.driver_data = (unsigned long) &btmtk_sdio_7961
+ */ },
+
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x790A)
+ /*,
+ *.driver_data = (unsigned long) &btmtk_sdio_790A
+ *.For sdio interface, WiFi & BT use the different
+ * PID to recognize their interface.
+ * WiFi : 7902, BT: 790A.
+ */ },
+
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(sdio, btmtk_sdio_tabls);
+
+#if BTMTK_SDIO_DEBUG
+#define RX_DEBUG_ENTRY_NUM 50
+enum {
+ CHISR_r_1 = 0,
+ CHISR_r_2,
+ CRPLR_r,
+ PD2HRM0R_r,
+ SDIO_DEBUG_CR_MAX,
+ RX_TIMESTAMP,
+ RX_BUF
+};
+
+struct rx_debug_struct {
+ char rx_intr_timestamp[HCI_SNOOP_TS_STR_LEN];
+ u32 cr[SDIO_DEBUG_CR_MAX];
+ u8 buf[16];
+};
+static struct rx_debug_struct rx_debug[RX_DEBUG_ENTRY_NUM];
+static int rx_debug_index;
+
+static int rx_done_cnt;
+static int tx_empty_cnt;
+static int intr_cnt;
+static int driver_own_cnt;
+static int fw_own_cnt;
+
+void rx_debug_print(void)
+{
+ int i;
+ int j = rx_debug_index;
+
+ BTMTK_ERR("%s: rx_done_cnt=%d, tx_empty_cnt=%d, intr_cnt=%d, driver_own_cnt=%d, fw_own_cnt=%d",
+ __func__, rx_done_cnt, tx_empty_cnt, intr_cnt, driver_own_cnt, fw_own_cnt);
+ for (i = 0; i < RX_DEBUG_ENTRY_NUM; i++) {
+ BTMTK_ERR("%02d: timestamp=%s, CHISR_r_1=0x%08x, CHISR_r_2=0x%08x, CRPLR=0x%08x, PD2HRM0R=0x%08x,",
+ i, rx_debug[j].rx_intr_timestamp,
+ rx_debug[j].cr[CHISR_r_1], rx_debug[j].cr[CHISR_r_2],
+ rx_debug[j].cr[CRPLR_r], rx_debug[j].cr[PD2HRM0R_r]);
+ BTMTK_ERR("buf = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ rx_debug[j].buf[0], rx_debug[j].buf[1], rx_debug[j].buf[2], rx_debug[j].buf[3],
+ rx_debug[j].buf[4], rx_debug[j].buf[5], rx_debug[j].buf[6], rx_debug[j].buf[7],
+ rx_debug[j].buf[8], rx_debug[j].buf[9], rx_debug[j].buf[10], rx_debug[j].buf[11],
+ rx_debug[j].buf[12], rx_debug[j].buf[13], rx_debug[j].buf[14], rx_debug[j].buf[15]);
+ if (j == 0)
+ j = RX_DEBUG_ENTRY_NUM;
+ j--;
+ }
+}
+
+void rx_debug_save(int type, u32 value, u8 *buf)
+{
+ switch (type) {
+ case CHISR_r_1:
+ case CHISR_r_2:
+ case CRPLR_r:
+ case PD2HRM0R_r:
+ rx_debug[rx_debug_index].cr[type] = value;
+ break;
+ case RX_TIMESTAMP:
+ rx_debug_index++;
+ if (rx_debug_index == RX_DEBUG_ENTRY_NUM)
+ rx_debug_index = 0;
+ btmtk_get_UTC_time_str(rx_debug[rx_debug_index].rx_intr_timestamp);
+ break;
+ case RX_BUF:
+ memset(rx_debug[rx_debug_index].buf, 0, 16);
+ memcpy(rx_debug[rx_debug_index].buf, buf, 16);
+ break;
+ }
+}
+#endif
+
+static void btmtk_sdio_dump_debug_register(struct btmtk_dev *bdev,
+ struct debug_reg_struct debug_reg)
+{
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ u32 value = 0, i = 0, count = 0;
+ static u32 reg_page[] = {0, 0};
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ count = debug_reg.num;
+ for (i = 0; i < count; i++) {
+ if (!debug_reg.reg[i].length)
+ continue;
+
+ switch (debug_reg.reg[i].length) {
+ case 1:
+ /* reg read address */
+ btmtk_sdio_readl(debug_reg.reg[i].content[0],
+ &value,
+ cif_dev->func);
+ BTMTK_INFO("%s R(0x%08X) = 0x%08X",
+ __func__,
+ debug_reg.reg[i].content[0], value);
+ break;
+ case 2:
+ /* write reg address and value */
+ btmtk_sdio_writel(debug_reg.reg[i].content[0],
+ debug_reg.reg[i].content[1],
+ cif_dev->func);
+ reg_page[0] = debug_reg.reg[i].content[0];
+ reg_page[1] = debug_reg.reg[i].content[1];
+ BTMTK_INFO("%s W(0x%08X) = 0x%08X",
+ __func__,
+ debug_reg.reg[i].content[0], debug_reg.reg[i].content[1]);
+ break;
+ case 3:
+ /* write reg and read reg */
+ btmtk_sdio_writel(debug_reg.reg[i].content[0],
+ debug_reg.reg[i].content[1],
+ cif_dev->func);
+ btmtk_sdio_readl(debug_reg.reg[i].content[2],
+ &value,
+ cif_dev->func);
+ BTMTK_INFO("%s W(0x%08X) = 0x%08X, W(0x%08X) = 0x%08X, R(0x%08X) = 0x%08X",
+ __func__,
+ reg_page[0], reg_page[1],
+ debug_reg.reg[i].content[0], debug_reg.reg[i].content[1],
+ debug_reg.reg[i].content[2], value);
+ break;
+ default:
+ BTMTK_WARN("%s: Unknown result: %d", __func__, debug_reg.reg[i].length);
+ break;
+ }
+ }
+}
+
+static void btmtk_sdio_dump_debug_sop(struct btmtk_dev *bdev)
+{
+ /* dump mcu_sleep_wakeup_debug(BGFSYS_status),
+ * only for PCIE, USB/SDIO not support
+ */
+ if (bdev == NULL) {
+ BTMTK_ERR("%s bdev is NULL", __func__);
+ return;
+ }
+
+ BTMTK_INFO("%s -debug sop dump start", __func__);
+ btmtk_sdio_dump_debug_register(bdev, bdev->debug_sop_reg_dump);
+ BTMTK_INFO("%s -debug sop dump end", __func__);
+}
+
+static void btmtk_sdio_cif_mutex_lock(struct btmtk_dev *bdev)
+{
+ SDIO_OPS_MUTEX_LOCK();
+}
+
+static void btmtk_sdio_cif_mutex_unlock(struct btmtk_dev *bdev)
+{
+ SDIO_OPS_MUTEX_UNLOCK();
+}
+
+void btmtk_sdio_set_no_fwn_own(struct btmtk_sdio_dev *cif_dev, int flag)
+{
+ if (cif_dev->no_fw_own != flag)
+ BTMTK_INFO("%s set no_fw_own %d", __func__, flag);
+ cif_dev->no_fw_own = flag;
+}
+
+static int btmtk_sdio_set_fw_own(struct btmtk_sdio_dev *cif_dev, int retry)
+{
+ /*Set fw own*/
+ int ret = 0;
+ u32 u32LoopCount = 0;
+ u32 u32PollNum = 0;
+ u32 u32CHLPCRValue = 0;
+ u32 u32PD2HRM0RValue = 0;
+ u32 ownValue = 0;
+ u32 i = 0;
+ u8 chlpcr_driver_own = 0;
+ u8 pd2hrm0r_driver_own = 0;
+
+ BTMTK_DBG("%s", __func__);
+
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_INIT);
+ if (cif_dev->no_fw_own)
+ return 0;
+
+ SDIO_OWN_MUTEX_LOCK();
+
+ /* For CHLPCR, bit 8 could help us to check driver own or fw own
+ * 0: COM driver doesn't have ownership
+ * 1: COM driver has ownership
+ */
+ ret = btmtk_sdio_readl(CHLPCR, &u32CHLPCRValue, cif_dev->func);
+ chlpcr_driver_own = ((u32CHLPCRValue & 0x100) == 0x100) ? 1 : 0;
+
+ if (cif_dev->patched == 1) {
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32PD2HRM0RValue, cif_dev->func);
+ pd2hrm0r_driver_own =
+ ((u32PD2HRM0RValue & PD2HRM0R_DRIVER_OWN)
+ == PD2HRM0R_DRIVER_OWN) ? 1 : 0;
+ } else {
+ pd2hrm0r_driver_own = 0;
+ }
+
+ BTMTK_DBG("CHLPCR: 0x%0x, PD2HRM0R: 0x%0x",
+ u32CHLPCRValue, u32PD2HRM0RValue);
+
+ if (!chlpcr_driver_own && !pd2hrm0r_driver_own) {
+ ret = 0;
+ goto unlock;
+ }
+
+ ownValue = 0x00000100;
+retry_own:
+ if (cif_dev->patched == 1) {
+ /* write CSICR to notify FW to set PD2HRM0R to 0 */
+ ret = btmtk_sdio_writel(CSICR, 1, cif_dev->func);
+ if (ret) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = btmtk_sdio_writel(CSICR, 0xC0, cif_dev->func);
+ if (ret) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ u32LoopCount = SET_OWN_LOOP_COUNT;
+ pd2hrm0r_driver_own = 0;
+ do {
+ usleep_range(200, 300);
+ u32LoopCount--;
+ u32PollNum++;
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32PD2HRM0RValue, cif_dev->func);
+ BTMTK_DBG("%s set driver own PD2HRM0R = 0x%0x", __func__, u32PD2HRM0RValue);
+ pd2hrm0r_driver_own =
+ ((u32PD2HRM0RValue & PD2HRM0R_DRIVER_OWN)
+ == PD2HRM0R_DRIVER_OWN) ? 1 : 0;
+ } while ((u32LoopCount > 0) && pd2hrm0r_driver_own);
+
+ if (pd2hrm0r_driver_own) {
+ if (retry > 0) {
+ BTMTK_WARN("%s retry set_check fw own(%d), PD2HRM0R:0x%x",
+ __func__, u32PollNum, u32PD2HRM0RValue);
+ for (i = 0; i < 3; i++)
+ DUMP_FW_PC(cif_dev);
+
+ retry--;
+ usleep_range(5*1000, 10*1000);
+ goto retry_own;
+ } else {
+ ret = -EINVAL;
+ }
+ }
+ }
+
+ /* Write CR for Driver or FW own */
+ if (ret == 0) {
+ ret = btmtk_sdio_writel(CHLPCR, ownValue, cif_dev->func);
+ if (ret) {
+ ret = -EINVAL;
+ goto done;
+ }
+ }
+done:
+#if BTMTK_SDIO_DEBUG
+ fw_own_cnt++;
+#endif
+ if (ret) {
+ BTMTK_ERR("%s set FW own fail", __func__);
+ btmtk_sdio_dump_debug_sop(cif_dev->bdev);
+ } else
+ BTMTK_DBG("%s set FW own success", __func__);
+
+unlock:
+ SDIO_OWN_MUTEX_UNLOCK();
+ return ret;
+}
+
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+static void btmtk_fw_own_timer(unsigned long arg)
+{
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)arg;
+
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_RUNNING);
+ wake_up_interruptible(&cif_dev->sdio_thread.wait_q);
+}
+#else
+static void btmtk_fw_own_timer(struct timer_list *timer)
+{
+ struct btmtk_sdio_dev *cif_dev = from_timer(cif_dev, timer, fw_own_timer);
+
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_RUNNING);
+ wake_up_interruptible(&cif_dev->sdio_thread.wait_q);
+}
+#endif
+
+static void btmtk_sdio_update_fw_own_timer(struct btmtk_sdio_dev *cif_dev)
+{
+ BTMTK_DBG("%s: update fw own timer", __func__);
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_INIT);
+ mod_timer(&cif_dev->fw_own_timer, jiffies + msecs_to_jiffies(FW_OWN_TIMEOUT));
+}
+
+static void btmtk_sdio_create_fw_own_timer(struct btmtk_sdio_dev *cif_dev)
+{
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+ init_timer(&cif_dev->fw_own_timer);
+ cif_dev->fw_own_timer.function = btmtk_fw_own_timer;
+ cif_dev->fw_own_timer.data = (unsigned long)cif_dev;
+#else
+ timer_setup(&cif_dev->fw_own_timer, btmtk_fw_own_timer, 0);
+#endif
+ BTMTK_INFO("%s end", __func__);
+}
+
+static void btmtk_sdio_delete_fw_own_timer(struct btmtk_sdio_dev *cif_dev)
+{
+ del_timer_sync(&cif_dev->fw_own_timer);
+ BTMTK_INFO("%s end", __func__);
+}
+
+static int btmtk_sdio_set_driver_own(struct btmtk_sdio_dev *cif_dev, int retry)
+{
+ /*Set driver own*/
+ int ret = 0;
+ u32 u32LoopCount = 0;
+ u32 u32PollNum = 0;
+ u32 u32CHLPCRValue = 0;
+ u32 u32PD2HRM0RValue = 0;
+ u32 ownValue = 0;
+ u32 i = 0;
+ u8 chlpcr_driver_own = 0;
+ u8 pd2hrm0r_driver_own = 0;
+
+ BTMTK_DBG("%s", __func__);
+
+ if (cif_dev->no_fw_own == 0)
+ btmtk_sdio_update_fw_own_timer(cif_dev);
+
+ SDIO_OWN_MUTEX_LOCK();
+ /* For CHLPCR, bit 8 could help us to check driver own or fw own
+ * 0: COM driver doesn't have ownership
+ * 1: COM driver has ownership
+ */
+ ret = btmtk_sdio_readl(CHLPCR, &u32CHLPCRValue, cif_dev->func);
+ chlpcr_driver_own = ((u32CHLPCRValue & 0x100) == 0x100) ? 1 : 0;
+
+ if (cif_dev->patched == 1) {
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32PD2HRM0RValue, cif_dev->func);
+ pd2hrm0r_driver_own =
+ ((u32PD2HRM0RValue & PD2HRM0R_DRIVER_OWN)
+ == PD2HRM0R_DRIVER_OWN) ? 1 : 0;
+ } else {
+ pd2hrm0r_driver_own = 1;
+ }
+
+ BTMTK_DBG("CHLPCR: 0x%0x, PD2HRM0R: 0x%0x",
+ u32CHLPCRValue, u32PD2HRM0RValue);
+
+ if (chlpcr_driver_own && pd2hrm0r_driver_own) {
+ ret = 0;
+ goto unlock;
+ }
+
+ ownValue = 0x00000200;
+retry_own:
+ /* Write CR for Driver or FW own */
+ ret = btmtk_sdio_writel(CHLPCR, ownValue, cif_dev->func);
+ if (ret) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ u32LoopCount = SET_OWN_LOOP_COUNT;
+ do {
+ usleep_range(200, 300);
+ u32LoopCount--;
+ u32PollNum++;
+ ret = btmtk_sdio_readl(CHLPCR, &u32CHLPCRValue, cif_dev->func);
+ BTMTK_DBG("%s set driver own CHLPCR = 0x%0x", __func__, u32CHLPCRValue);
+ chlpcr_driver_own = ((u32CHLPCRValue & 0x100) == 0x100) ? 1 : 0;
+ if (cif_dev->patched == 1) {
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32PD2HRM0RValue, cif_dev->func);
+ BTMTK_DBG("%s set driver own PD2HRM0R = 0x%0x", __func__, u32PD2HRM0RValue);
+ pd2hrm0r_driver_own =
+ ((u32PD2HRM0RValue & PD2HRM0R_DRIVER_OWN)
+ == PD2HRM0R_DRIVER_OWN) ? 1 : 0;
+ } else {
+ pd2hrm0r_driver_own = 1;
+ }
+ } while ((u32LoopCount > 0) && (!chlpcr_driver_own || !pd2hrm0r_driver_own));
+
+ if (!chlpcr_driver_own || !pd2hrm0r_driver_own) {
+ if (retry > 0) {
+ BTMTK_WARN("%s retry set_check driver own(%d), CHLPCR:0x%x, PD2HRM0R:0x%x",
+ __func__, u32PollNum, u32CHLPCRValue, u32PD2HRM0RValue);
+ for (i = 0; i < 3; i++)
+ DUMP_FW_PC(cif_dev);
+
+ retry--;
+ usleep_range(5*1000, 10*1000);
+ goto retry_own;
+ } else {
+ ret = -EINVAL;
+ }
+ }
+done:
+
+#if BTMTK_SDIO_DEBUG
+ driver_own_cnt++;
+#endif
+ if (ret) {
+ BTMTK_ERR("%s set driver own fail", __func__);
+ for (i = 0; i < 8; i++) {
+ DUMP_FW_PC(cif_dev);
+ msleep(200);
+ }
+ btmtk_sdio_dump_debug_sop(cif_dev->bdev);
+ } else
+ BTMTK_DBG("%s set driver own success", __func__);
+unlock:
+ SDIO_OWN_MUTEX_UNLOCK();
+
+ return ret;
+}
+
+static int btmtk_sdio_keep_driver_own(struct btmtk_sdio_dev *cif_dev, int enable)
+{
+ int ret = 0;
+
+ if (enable == 1) {
+ btmtk_sdio_set_no_fwn_own(cif_dev, 1);
+ ret = btmtk_sdio_set_driver_own(cif_dev, RETRY_TIMES);
+ } else {
+ btmtk_sdio_set_no_fwn_own(cif_dev, 0);
+ ret = btmtk_sdio_set_fw_own(cif_dev, RETRY_TIMES);
+ }
+ return ret;
+}
+
+static int btmtk_sdio_read_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ int ret = 0;
+ u8 cmd[READ_REGISTER_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x0C,
+ 0x01, 0x08, 0x08, 0x00,
+ 0x02, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00};
+
+ u8 event[READ_REGISTER_EVT_HDR_LEN] = {0x04, 0xE4, 0x10, 0x02,
+ 0x08, 0x0C, 0x00, 0x00,
+ 0x00, 0x00, 0x01};
+
+ /* To-do using structure for sdio header
+ * struct btmtk_sdio_hdr *sdio_hdr;
+ * sdio_hdr = (void *) cmd;
+ * sdio_hdr->len = cpu_to_le16(skb->len);
+ * sdio_hdr->reserved = cpu_to_le16(0);
+ * sdio_hdr->bt_type = hci_skb_pkt_type(skb);
+ */
+
+ BTMTK_INFO("%s: read cr %x", __func__, reg);
+
+ memcpy(&cmd[MCU_ADDRESS_OFFSET_CMD], ®, sizeof(reg));
+
+ ret = btmtk_main_send_cmd(bdev, cmd, READ_REGISTER_CMD_LEN, event, READ_REGISTER_EVT_HDR_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ memcpy(val, bdev->io_buf + MCU_ADDRESS_OFFSET_EVT - HCI_TYPE_SIZE, sizeof(u32));
+ *val = le32_to_cpu(*val);
+
+ BTMTK_INFO("%s: reg=%x, value=0x%08x", __func__, reg, *val);
+
+ return ret;
+}
+
+static int btmtk_sdio_write_register(struct btmtk_dev *bdev, u32 reg, u32 val)
+{
+ BTMTK_INFO("%s: reg=%x, value=0x%08x, not support", __func__, reg, val);
+ return 0;
+}
+
+static int btmtk_cif_allocate_memory(struct btmtk_sdio_dev *cif_dev)
+{
+ int ret = -1;
+
+ if (cif_dev->transfer_buf == NULL) {
+ cif_dev->transfer_buf = kzalloc(URB_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->transfer_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->transfer_buf)", __func__);
+ goto end;
+ }
+ }
+
+ if (cif_dev->sdio_packet == NULL) {
+ cif_dev->sdio_packet = kzalloc(URB_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->sdio_packet) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->transfer_buf)", __func__);
+ goto err;
+ }
+ }
+
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+err:
+ kfree(cif_dev->transfer_buf);
+ cif_dev->transfer_buf = NULL;
+end:
+ return ret;
+}
+
+static void btmtk_cif_free_memory(struct btmtk_sdio_dev *cif_dev)
+{
+ kfree(cif_dev->transfer_buf);
+ cif_dev->transfer_buf = NULL;
+
+ kfree(cif_dev->sdio_packet);
+ cif_dev->sdio_packet = NULL;
+
+ BTMTK_INFO("%s: Success", __func__);
+}
+
+int btmtk_sdio_read_wifi_mcu_pc(u8 PcLogSel, u32 *val)
+{
+ int ret = 0;
+ unsigned int value = 0;
+ int state = BTMTK_STATE_INIT;
+
+ if (!g_sdio_dev.func) {
+ BTMTK_ERR("%s g_sdio_dev.func is NULL!", __func__);
+ return -EINVAL;
+ }
+
+ if (!g_sdio_dev.bdev) {
+ BTMTK_ERR("%s bdev is NULL!", __func__);
+ return -EINVAL;
+ }
+
+ state = btmtk_get_chip_state(g_sdio_dev.bdev);
+ if (state != BTMTK_STATE_WORKING) {
+ BTMTK_WARN("%s state is invalid, state = %d!", __func__, state);
+ return -ENODEV;
+ }
+
+ SDIO_DEBUG_MUTEX_LOCK();
+
+ ret = btmtk_sdio_readl(CONDBGCR_SEL, &value, g_sdio_dev.func);
+ value |= SDIO_CTRL_EN;
+ value &= WM_MONITER_SEL;
+ value &= PC_MONITER_SEL;
+ value = PC_IDX_SWH(value, PcLogSel);
+
+ ret = btmtk_sdio_writel(CONDBGCR_SEL, value, g_sdio_dev.func);
+ ret = btmtk_sdio_readl(CONDBGCR, val, g_sdio_dev.func);
+
+ SDIO_DEBUG_MUTEX_UNLOCK();
+
+ return 0;
+}
+EXPORT_SYMBOL(btmtk_sdio_read_wifi_mcu_pc);
+
+int btmtk_sdio_read_bt_mcu_pc(u32 *val)
+{
+ if (!g_sdio_dev.func)
+ return -EINVAL;
+
+ SDIO_DEBUG_MUTEX_LOCK();
+
+ if (is_mt7902(g_sdio_dev.bdev->chip_id))
+ btmtk_sdio_writel(0x34, 0x01, g_sdio_dev.func);
+
+ btmtk_sdio_writel(0x30, 0xFD, g_sdio_dev.func);
+ btmtk_sdio_readl(0x2c, val, g_sdio_dev.func);
+
+ SDIO_DEBUG_MUTEX_UNLOCK();
+
+ return 0;
+}
+EXPORT_SYMBOL(btmtk_sdio_read_bt_mcu_pc);
+
+/**
+ * Read power status(not conn infra pc).
+ */
+int btmtk_sdio_read_conn_infra_pc(u32 *val)
+{
+ if (!g_sdio_dev.func)
+ return -EINVAL;
+
+ SDIO_DEBUG_MUTEX_LOCK();
+
+ if (is_mt7902(g_sdio_dev.bdev->chip_id))
+ btmtk_sdio_writel(0x34, 0x04, g_sdio_dev.func);
+ else
+ btmtk_sdio_writel(0x44, 0, g_sdio_dev.func);
+
+ btmtk_sdio_writel(0x3C, 0x9F1E0000, g_sdio_dev.func);
+ btmtk_sdio_readl(0x38, val, g_sdio_dev.func);
+
+ SDIO_DEBUG_MUTEX_UNLOCK();
+
+ return 0;
+}
+EXPORT_SYMBOL(btmtk_sdio_read_conn_infra_pc);
+
+typedef bool (*wifi_driver_own)(uint8_t enable);
+static wifi_driver_own wifi_driver_own_ptr;
+static void btmtk_sdio_set_wifi_driver_own(uint8_t enable)
+{
+ if (!wifi_driver_own_ptr)
+ wifi_driver_own_ptr =
+ (wifi_driver_own)btmtk_kallsyms_lookup_name("halPreventFwOwnEn");
+
+ if (wifi_driver_own_ptr) {
+ BTMTK_INFO("%s set wifi own to %d", __func__, enable);
+ wifi_driver_own_ptr(enable);
+ } else {
+ BTMTK_INFO("%s wifi_driver_own_ptr is NULL", __func__);
+ }
+}
+
+int btmtk_sdio_set_driver_own_for_subsys_reset(int enable)
+{
+ if (!g_sdio_dev.func)
+ return -ENODEV;
+
+ BTMTK_INFO("%s enter! enable = %d", __func__, enable);
+ if (enable == 1) {
+ btmtk_sdio_set_no_fwn_own(&g_sdio_dev, 1);
+ return 0;
+ } else if (enable == 0) {
+ btmtk_sdio_set_no_fwn_own(&g_sdio_dev, 0);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL(btmtk_sdio_set_driver_own_for_subsys_reset);
+
+static int btmtk_sdio_open(struct hci_dev *hdev)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s enter!", __func__);
+ skb_queue_purge(&cif_dev->tx_queue);
+
+#if BTMTK_SDIO_DEBUG
+ rx_done_cnt = 0;
+ tx_empty_cnt = 0;
+ intr_cnt = 0;
+ driver_own_cnt = 0;
+ fw_own_cnt = 0;
+#endif
+
+ return 0;
+}
+
+static int btmtk_sdio_close(struct hci_dev *hdev)
+{
+ return 0;
+}
+
+static void btmtk_sdio_open_done(struct btmtk_dev *bdev)
+{
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s enter!", __func__);
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ /* We don't need to enable buffer mode during bring-up stage. */
+ BTMTK_INFO("SKIP buffer mode");
+#else
+ (void)btmtk_buffer_mode_send(cif_dev->buffer_mode);
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+}
+
+static int btmtk_sdio_writesb(u32 offset, u8 *val, int len, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("%s func is NULL", __func__);
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ ret = sdio_writesb(func, offset, val, len);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static int btmtk_sdio_readsb(u32 offset, u8 *val, int len, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("%s func is NULL", __func__);
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ ret = sdio_readsb(func, val, offset, len);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+int btmtk_sdio_writeb(u32 offset, u8 val, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("%s func is NULL", __func__);
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ sdio_writeb(func, val, offset, &ret);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static int btmtk_sdio_writel(u32 offset, u32 val, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("%s func is NULL", __func__);
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ sdio_writel(func, val, offset, &ret);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static int btmtk_sdio_readl(u32 offset, u32 *val, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("func is NULL");
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ *val = sdio_readl(func, offset, &ret);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static int btmtk_sdio_readb(u32 offset, u8 *val, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 retry_count = 0;
+
+ if (func == NULL) {
+ BTMTK_ERR("%s func is NULL", __func__);
+ return -EIO;
+ }
+
+ do {
+ sdio_claim_host(func);
+ *val = sdio_readb(func, offset, &ret);
+ sdio_release_host(func);
+ retry_count++;
+ if (retry_count > SDIO_RW_RETRY_COUNT) {
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+ break;
+ }
+ } while (ret);
+
+ return ret;
+}
+
+static void btmtk_sdio_print_debug_sr(struct btmtk_sdio_dev *cif_dev)
+{
+ u32 ret = 0;
+ u32 CCIR_Value = 0;
+ u32 CHLPCR_Value = 0;
+ u32 CSDIOCSR_Value = 0;
+ u32 CHISR_Value = 0;
+ u32 CHIER_Value = 0;
+ u32 CTFSR_Value = 0;
+ u32 CRPLR_Value = 0;
+ u32 SWPCDBGR_Value = 0;
+ unsigned char X0_Value = 0;
+ unsigned char X4_Value = 0;
+ unsigned char X5_Value = 0;
+ unsigned char F8_Value = 0;
+ unsigned char F9_Value = 0;
+ unsigned char FA_Value = 0;
+ unsigned char FB_Value = 0;
+ unsigned char FC_Value = 0;
+ unsigned char FD_Value = 0;
+ unsigned char FE_Value = 0;
+ unsigned char FF_Value = 0;
+
+ ret = btmtk_sdio_readl(CCIR, &CCIR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CHLPCR, &CHLPCR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CSDIOCSR, &CSDIOCSR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CHISR, &CHISR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CHIER, &CHIER_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CTFSR, &CTFSR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(CRPLR, &CRPLR_Value, cif_dev->func);
+ ret = btmtk_sdio_readl(SWPCDBGR, &SWPCDBGR_Value, cif_dev->func);
+ sdio_claim_host(cif_dev->func);
+ X0_Value = sdio_f0_readb(cif_dev->func, 0x00, &ret);
+ X4_Value = sdio_f0_readb(cif_dev->func, 0x04, &ret);
+ X5_Value = sdio_f0_readb(cif_dev->func, 0x05, &ret);
+ F8_Value = sdio_f0_readb(cif_dev->func, 0xF8, &ret);
+ F9_Value = sdio_f0_readb(cif_dev->func, 0xF9, &ret);
+ FA_Value = sdio_f0_readb(cif_dev->func, 0xFA, &ret);
+ FB_Value = sdio_f0_readb(cif_dev->func, 0xFB, &ret);
+ FC_Value = sdio_f0_readb(cif_dev->func, 0xFC, &ret);
+ FD_Value = sdio_f0_readb(cif_dev->func, 0xFD, &ret);
+ FE_Value = sdio_f0_readb(cif_dev->func, 0xFE, &ret);
+ FF_Value = sdio_f0_readb(cif_dev->func, 0xFF, &ret);
+ sdio_release_host(cif_dev->func);
+ BTMTK_INFO("CCIR: 0x%x, CHLPCR: 0x%x, CSDIOCSR: 0x%x, CHISR: 0x%x",
+ CCIR_Value, CHLPCR_Value, CSDIOCSR_Value, CHISR_Value);
+ BTMTK_INFO("CHIER: 0x%x, CTFSR: 0x%x, CRPLR: 0x%x, SWPCDBGR: 0x%x",
+ CHIER_Value, CTFSR_Value, CRPLR_Value, SWPCDBGR_Value);
+ BTMTK_INFO("CCCR 00: 0x%x, 04: 0x%x, 05: 0x%x",
+ X0_Value, X4_Value, X5_Value);
+ BTMTK_INFO("F8: 0x%x, F9: 0x%x, FA: 0x%x, FB: 0x%x",
+ F8_Value, F9_Value, FA_Value, FB_Value);
+ BTMTK_INFO("FC: 0x%x, FD: 0x%x, FE: 0x%x, FF: 0x%x",
+ FC_Value, FD_Value, FE_Value, FF_Value);
+}
+
+static int btmtk_sdio_enable_interrupt(int enable, struct sdio_func *func)
+{
+ u32 ret = 0;
+ u32 cr_value = 0;
+
+ BTMTK_DBG("%s enable=%d", __func__, enable);
+ if (enable)
+ cr_value |= C_FW_INT_EN_SET;
+ else
+ cr_value |= C_FW_INT_EN_CLEAR;
+
+ ret = btmtk_sdio_writel(CHLPCR, cr_value, func);
+
+ return ret;
+}
+
+int btmtk_sdio_send_cmd(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type)
+{
+ int ret = 0;
+ u32 crAddr = 0, crValue = 0;
+ ulong flags;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+
+ /* for fw assert */
+ u8 fw_assert_cmd[FW_ASSERT_CMD_LEN] = { 0x01, 0x5B, 0xFD, 0x00 };
+ u8 fw_assert_cmd1[FW_ASSERT_CMD1_LEN] = { 0x01, 0x6F, 0xFC, 0x05, 0x01, 0x02, 0x01, 0x00, 0x08 };
+ /* for read/write CR */
+ u8 notify_alt_evt[NOTIFY_ALT_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0x03, 0x0c, 0x00};
+ struct sk_buff *evt_skb;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("bdev is NULL");
+ ret = -1;
+ goto exit;
+ }
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("cif_dev is NULL, bdev=%p", bdev);
+ ret = -1;
+ goto exit;
+ }
+
+ /* For read write CR */
+ if (skb->len > 9) {
+ if (skb->data[0] == 0x01 && skb->data[1] == 0x6f && skb->data[2] == 0xfc &&
+ skb->data[3] == 0x0D && skb->data[4] == 0x01 &&
+ skb->data[5] == 0xff && skb->data[6] == 0x09 &&
+ skb->data[7] == 0x00 && skb->data[8] == 0x02) {
+ crAddr = ((skb->data[9] & 0xff) << 24) + ((skb->data[10] & 0xff) << 16)
+ + ((skb->data[11] & 0xff) << 8) + (skb->data[12] & 0xff);
+ crValue = ((skb->data[13] & 0xff) << 24) + ((skb->data[14] & 0xff) << 16)
+ + ((skb->data[15] & 0xff) << 8) + (skb->data[16] & 0xff);
+
+ BTMTK_INFO("%s crAddr = 0x%08x crValue = 0x%08x",
+ __func__, crAddr, crValue);
+
+ btmtk_sdio_writel(crAddr, crValue, cif_dev->func);
+ evt_skb = skb_copy(skb, GFP_KERNEL);
+ if (evt_skb) {
+ bt_cb(evt_skb)->pkt_type = notify_alt_evt[0];
+ notify_alt_evt[3] = (crValue & 0xFF000000) >> 24;
+ notify_alt_evt[4] = (crValue & 0x00FF0000) >> 16;
+ notify_alt_evt[5] = (crValue & 0x0000FF00) >> 8;
+ notify_alt_evt[6] = (crValue & 0x000000FF);
+ memcpy(evt_skb->data, ¬ify_alt_evt[1], NOTIFY_ALT_EVT_LEN - 1);
+ evt_skb->len = NOTIFY_ALT_EVT_LEN - 1;
+ hci_recv_frame(bdev->hdev, evt_skb);
+ kfree_skb(skb);
+ skb = NULL;
+ } else {
+ BTMTK_ERR("%s skb_copy failed", __func__);
+ }
+ goto exit;
+ } else if (skb->data[0] == 0x01 && skb->data[1] == 0x6f && skb->data[2] == 0xfc &&
+ skb->data[3] == 0x09 && skb->data[4] == 0x01 &&
+ skb->data[5] == 0xff && skb->data[6] == 0x05 &&
+ skb->data[7] == 0x00 && skb->data[8] == 0x01) {
+
+ crAddr = ((skb->data[9] & 0xff) << 24) +
+ ((skb->data[10] & 0xff) << 16) +
+ ((skb->data[11] & 0xff) << 8) +
+ (skb->data[12] & 0xff);
+
+ btmtk_sdio_readl(crAddr, &crValue, cif_dev->func);
+ BTMTK_INFO("%s read crAddr = 0x%08x crValue = 0x%08x",
+ __func__, crAddr, crValue);
+ evt_skb = skb_copy(skb, GFP_KERNEL);
+ if (evt_skb) {
+ bt_cb(evt_skb)->pkt_type = notify_alt_evt[0];
+ /* memcpy(¬ify_alt_evt[2], &crValue, sizeof(crValue)); */
+ notify_alt_evt[3] = (crValue & 0xFF000000) >> 24;
+ notify_alt_evt[4] = (crValue & 0x00FF0000) >> 16;
+ notify_alt_evt[5] = (crValue & 0x0000FF00) >> 8;
+ notify_alt_evt[6] = (crValue & 0x000000FF);
+ memcpy(evt_skb->data, ¬ify_alt_evt[1], NOTIFY_ALT_EVT_LEN - 1);
+ evt_skb->len = NOTIFY_ALT_EVT_LEN - 1;
+ hci_recv_frame(bdev->hdev, evt_skb);
+ kfree_skb(skb);
+ skb = NULL;
+ } else {
+ BTMTK_ERR("%s skb_copy failed", __func__);
+ }
+ goto exit;
+ }
+ }
+
+ /* error handle, can't do free skb at this point, it will be released at hci_send_frame when failed */
+ if (!atomic_read(&cif_dev->sdio_thread.thread_status)) {
+ BTMTK_WARN("%s main thread already stopped, don't send cmd anymore!!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ if (skb->data[0] == MTK_HCI_ACLDATA_PKT && skb->data[1] == 0x00 && skb->data[2] == 0x44) {
+ /* it's for ble iso, remove speicific header
+ * 02 00 44 len len + payload to 05 + payload
+ */
+ skb_pull(skb, 4);
+ skb->data[0] = HCI_ISO_PKT;
+ }
+
+ if ((skb->len == FW_ASSERT_CMD_LEN &&
+ !memcmp(skb->data, fw_assert_cmd, FW_ASSERT_CMD_LEN))
+ || (skb->len == FW_ASSERT_CMD1_LEN &&
+ !memcmp(skb->data, fw_assert_cmd1, FW_ASSERT_CMD1_LEN))) {
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: Trigger FW assert, dump CR", __func__);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_print();
+#endif
+ btmtk_sdio_keep_driver_own(cif_dev, 1);
+ btmtk_sdio_print_debug_sr(cif_dev);
+ btmtk_sdio_dump_debug_sop(bdev);
+ }
+
+ spin_lock_irqsave(&bdev->txlock, flags);
+ skb_queue_tail(&cif_dev->tx_queue, skb);
+ spin_unlock_irqrestore(&bdev->txlock, flags);
+ wake_up_interruptible(&cif_dev->sdio_thread.wait_q);
+
+exit:
+ return ret;
+}
+
+static int btmtk_cif_recv_evt(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ u32 u32ReadCRValue = 0;
+ u32 u32ReadCRLEN = 0;
+ u32 sdio_header_length = 0;
+ int rx_length = 0;
+ int payload = 0;
+ u16 hci_pkt_len = 0;
+ u8 hci_type = 0;
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+
+ /* keep polling method */
+ /* If interrupt method is working, we can remove it */
+ ret = btmtk_sdio_readl(CHISR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_DBG("%s: loop Get CHISR 0x%08X",
+ __func__, u32ReadCRValue);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(CHISR_r_2, u32ReadCRValue, NULL);
+#endif
+
+ ret = btmtk_sdio_readl(CRPLR, &u32ReadCRLEN, cif_dev->func);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(CRPLR_r, u32ReadCRLEN, NULL);
+#endif
+
+ rx_length = (u32ReadCRLEN & RX_PKT_LEN) >> 16;
+ if (rx_length == 0xFFFF || rx_length == 0) {
+ BTMTK_WARN("%s: rx_length = %d, error return -EIO", __func__, rx_length);
+ return -EIO;
+ }
+
+ BTMTK_DBG("%s: u32ReadCRValue = %08X", __func__, u32ReadCRValue);
+ u32ReadCRValue &= 0xFFFB;
+ ret = btmtk_sdio_writel(CHISR, u32ReadCRValue, cif_dev->func);
+ BTMTK_DBG("%s: write = %08X", __func__, u32ReadCRValue);
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32ReadCRValue, cif_dev->func);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(PD2HRM0R_r, u32ReadCRValue, NULL);
+#endif
+
+ ret = btmtk_sdio_readsb(CRDR, cif_dev->transfer_buf, rx_length, cif_dev->func);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(RX_BUF, 0, cif_dev->transfer_buf);
+#endif
+ sdio_header_length = (cif_dev->transfer_buf[1] << 8);
+ sdio_header_length |= cif_dev->transfer_buf[0];
+ if (sdio_header_length != rx_length) {
+ BTMTK_ERR("%s sdio header length %d, rx_length %d mismatch, trigger assert",
+ __func__, sdio_header_length, rx_length);
+ BTMTK_INFO_RAW(cif_dev->transfer_buf, rx_length, "%s: raw data is :", __func__);
+ btmtk_send_assert_cmd(bdev);
+ return -EIO;
+ }
+
+ BTMTK_DBG_RAW(cif_dev->transfer_buf, rx_length, "%s: raw data is :", __func__);
+
+ hci_type = cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE];
+ hci_pkt_len = get_pkt_len(hci_type, &cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 1]) + 1;
+ if (hci_type == HCI_ISO_PKT) {
+ hci_pkt_len -= 1;
+ /* Add ACL header*/
+ bdev->io_buf[0] = HCI_ACLDATA_PKT;
+ bdev->io_buf[1] = 0x00;
+ bdev->io_buf[2] = 0x44;
+ bdev->io_buf[3] = (hci_pkt_len & 0x00ff);
+ bdev->io_buf[4] = ((hci_pkt_len & 0xff00) >> 8);
+ memcpy(bdev->io_buf + 5, cif_dev->transfer_buf + MTK_SDIO_PACKET_HEADER_SIZE + 1, hci_pkt_len);
+ memset(cif_dev->transfer_buf, 0, URB_MAX_BUFFER_SIZE);
+ hci_pkt_len += 5;
+ memcpy(cif_dev->transfer_buf + MTK_SDIO_PACKET_HEADER_SIZE, bdev->io_buf, hci_pkt_len);
+ BTMTK_DBG_RAW(cif_dev->transfer_buf, hci_pkt_len, "%s: raw data is :", __func__);
+ }
+#if 0
+ switch (hci_type) {
+ /* Please reference hci header format
+ * A = len
+ * acl : 02 xx xx AA AA + payload
+ * sco : 03 xx xx AA + payload
+ * evt : 04 xx AA + payload
+ * ISO : 05 xx xx AA AA + payload
+ */
+ case HCI_ACLDATA_PKT:
+ hci_pkt_len = cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 3] +
+ (cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 4] << 8) + 5;
+ break;
+ case HCI_SCODATA_PKT:
+ hci_pkt_len = cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 4] + 4;
+ break;
+ case HCI_EVENT_PKT:
+ hci_pkt_len = cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 2] + 3;
+ break;
+ case HCI_ISO_PKT:
+ hci_pkt_len = cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 3] +
+ (cif_dev->transfer_buf[MTK_SDIO_PACKET_HEADER_SIZE + 4] << 8) + 4;
+ bdev->io_buf[0] = HCI_ACLDATA_PKT;
+ bdev->io_buf[1] = 0x00;
+ bdev->io_buf[2] = 0x44;
+ bdev->io_buf[3] = (hci_pkt_len & 0x00ff);
+ bdev->io_buf[4] = ((hci_pkt_len & 0xff00) >> 8);
+ memcpy(bdev->io_buf + 5, cif_dev->transfer_buf + MTK_SDIO_PACKET_HEADER_SIZE + 1, hci_pkt_len);
+ memset(cif_dev->transfer_buf, 0, URB_MAX_BUFFER_SIZE);
+ hci_pkt_len += 5;
+ memcpy(cif_dev->transfer_buf + MTK_SDIO_PACKET_HEADER_SIZE, bdev->io_buf, hci_pkt_len);
+ BTMTK_DBG_RAW(cif_dev->transfer_buf, hci_pkt_len, "%s: raw data is :", __func__);
+ break;
+ }
+#endif
+ ret = hci_pkt_len;
+ bdev->recv_evt_len = hci_pkt_len;
+
+ BTMTK_DBG("%s sdio header length %d, rx_length %d, hci_pkt_len = %d",
+ __func__, sdio_header_length, rx_length, hci_pkt_len);
+ ret = btmtk_recv(bdev->hdev, cif_dev->transfer_buf + MTK_SDIO_PACKET_HEADER_SIZE, hci_pkt_len);
+ if (cif_dev->transfer_buf[4] == HCI_EVENT_PKT) {
+ payload = rx_length - cif_dev->transfer_buf[6] - 3;
+ ret = rx_length - MTK_SDIO_PACKET_HEADER_SIZE - payload;
+ }
+
+ BTMTK_DBG("%s: done", __func__);
+ return ret;
+}
+
+int btmtk_sdio_event_filter(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ const u8 read_address_event[READ_ADDRESS_EVT_HDR_LEN] = { 0x4, 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00 };
+ u8 *p = NULL, *pend = NULL;
+ u8 attr_len, attr_type;
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE &&
+ skb->len >= event_need_compare_len) {
+ if (memcmp(skb->data, &read_address_event[1], READ_ADDRESS_EVT_HDR_LEN - 1) == 0
+ && (skb->len == (READ_ADDRESS_EVT_HDR_LEN - 1 + BD_ADDRESS_SIZE))) {
+ memcpy(bdev->bdaddr, &skb->data[READ_ADDRESS_EVT_PAYLOAD_OFFSET - 1], BD_ADDRESS_SIZE);
+ BTMTK_INFO("%s: GET BDADDR = "MACSTR, __func__, MAC2STR(bdev->bdaddr));
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ } else if (memcmp(skb->data, event_need_compare,
+ event_need_compare_len) == 0) {
+ /* if it is wobx debug event, just print in kernel log, drop it
+ * by driver, don't send to stack
+ */
+ if (skb->data[0] == WOBLE_DEBUG_EVT_TYPE) {
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: wobx debug log:", __func__);
+ /* parse WoBX debug log */
+ p = &skb->data[1];
+ pend = p + skb->data[1];
+ while (p < pend) {
+ attr_len = *(p + 1);
+ attr_type = *(p + 2);
+ BTMTK_INFO("attr_len = 0x%x, attr_type = 0x%x", attr_len, attr_type);
+ switch (attr_type) {
+ case WOBX_TRIGGER_INFO_ADDR_TYPE:
+ break;
+ case WOBX_TRIGGER_INFO_ADV_DATA_TYPE:
+ break;
+ case WOBX_TRIGGER_INFO_TRACE_LOG_TYPE:
+ break;
+ case WOBX_TRIGGER_INFO_SCAN_LOG_TYPE:
+ break;
+ case WOBX_TRIGGER_INFO_TRIGGER_CNT_TYPE:
+ BTMTK_INFO("wakeup times(via BT) = %02X%02X%02X%02X",
+ *(p + 6), *(p + 5), *(p + 4), *(p + 3));
+ break;
+ default:
+ BTMTK_ERR("%s: unexpected attribute type(0x%x)", __func__, attr_type);
+ return 1;
+ }
+ p += 1 + attr_len; // 1: len
+ }
+ }
+
+ /* If driver need to check result from skb, it can get from io_buf */
+ /* Such as chip_id, fw_version, etc. */
+ bdev->io_buf[0] = bt_cb(skb)->pkt_type;
+ memcpy(&bdev->io_buf[1], skb->data, skb->len);
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ BTMTK_DBG("%s, compare success", __func__);
+ } else {
+ if (skb->data[0] != BLE_EVT_TYPE) {
+ /* Don't care BLE event */
+ BTMTK_INFO("%s compare fail", __func__);
+ BTMTK_INFO_RAW(event_need_compare, event_need_compare_len,
+ "%s: event_need_compare:", __func__);
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: skb->data:", __func__);
+ }
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int btmtk_sdio_send_and_recv(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type)
+{
+ unsigned long comp_event_timo = 0, start_time = 0;
+ int ret = -1;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ if (event) {
+ if (event_len > EVENT_COMPARE_SIZE) {
+ BTMTK_ERR("%s, event_len (%d) > EVENT_COMPARE_SIZE(%d), error",
+ __func__, event_len, EVENT_COMPARE_SIZE);
+ ret = -1;
+ goto exit;
+ }
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE;
+ memcpy(event_need_compare, event + 1, event_len - 1);
+ event_need_compare_len = event_len - 1;
+
+ start_time = jiffies;
+ /* check hci event /wmt event for SDIO/UART interface, check hci
+ * event for USB interface
+ */
+ comp_event_timo = jiffies + msecs_to_jiffies(WOBLE_COMP_EVENT_TIMO);
+ BTMTK_DBG("event_need_compare_len %d, event_compare_status %d",
+ event_need_compare_len, event_compare_status);
+ } else {
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ }
+
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send, len = %d", __func__, skb->len);
+
+ ret = btmtk_sdio_send_cmd(bdev, skb, delay, retry, pkt_type);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_sdio_send_cmd failed!!", __func__);
+ goto exit;
+ }
+
+ do {
+ /* check if event_compare_success */
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS) {
+ ret = 0;
+ break;
+ }
+
+ /* error handle*/
+ if (!atomic_read(&cif_dev->sdio_thread.thread_status)) {
+ BTMTK_WARN("%s main thread already stopped, don't wait evt anymore!!", __func__);
+ ret = -ERRNUM;
+ goto exit;
+ }
+
+ usleep_range(10, 100);
+ } while (time_before(jiffies, comp_event_timo));
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE &&
+ atomic_read(&cif_dev->sdio_thread.thread_status)) {
+ BTMTK_ERR("%s wait expect event timeout!!", __func__);
+ ret = -ERRNUM;
+ goto fw_assert;
+ }
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NOTHING_NEED_COMPARE;
+ goto exit;
+fw_assert:
+ btmtk_send_assert_cmd(bdev);
+exit:
+ return ret;
+}
+
+static void btmtk_sdio_interrupt(struct sdio_func *func)
+{
+ struct btmtk_dev *bdev;
+ struct btmtk_sdio_dev *cif_dev;
+
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(RX_TIMESTAMP, 0, NULL);
+ intr_cnt++;
+#endif
+
+ bdev = sdio_get_drvdata(func);
+ if (!bdev)
+ return;
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ btmtk_sdio_enable_interrupt(0, cif_dev->func);
+ atomic_set(&cif_dev->int_count, 1);
+ wake_up_interruptible(&cif_dev->sdio_thread.wait_q);
+}
+
+static int btmtk_sdio_load_fw_patch_using_dma(struct btmtk_dev *bdev, u8 *image,
+ u8 *fwbuf, int section_dl_size, int section_offset)
+{
+ int cur_len = 0;
+ int ret = -1;
+ s32 sent_len = 0;
+ s32 sdio_len = 0;
+ s32 next_len = 0;
+ u32 u32ReadCRValue = 0;
+ u32 block_count = 0;
+ u32 redundant = 0;
+ u32 delay_count = 0;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ u8 cmd[LD_PATCH_CMD_LEN] = {0x02, 0x6F, 0xFC, 0x05, 0x00, 0x01, 0x01, 0x01, 0x00, PATCH_PHASE3};
+ u8 event[LD_PATCH_EVT_LEN] = {0x04, 0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[7] is status*/
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ if (bdev == NULL || image == NULL || fwbuf == NULL) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ return -1;
+ }
+
+ BTMTK_INFO("%s: loading rom patch... start", __func__);
+ btmtk_sdio_enable_interrupt(0, cif_dev->func);
+ while (section_dl_size != cur_len) {
+ if (!atomic_read(&cif_dev->tx_rdy)) {
+ ret = btmtk_sdio_readl(CHISR, &u32ReadCRValue, cif_dev->func);
+ if ((TX_EMPTY & u32ReadCRValue) != 0) {
+ ret = btmtk_sdio_writel(CHISR, (TX_EMPTY | TX_COMPLETE_COUNT), cif_dev->func);
+ if (ret != 0) {
+ BTMTK_ERR("%s: btmtk_sdio_writel fail", __func__);
+ goto enable_intr;
+ }
+ atomic_set(&cif_dev->tx_rdy, 1);
+ } else if (delay_count > 1000) {
+ BTMTK_ERR("%s: delay_count > 1000", __func__);
+ goto enable_intr;
+ } else {
+ usleep_range(100, 200);
+ ++delay_count;
+ continue;
+ }
+ }
+
+ sent_len = (section_dl_size - cur_len) >= (UPLOAD_PATCH_UNIT - MTK_SDIO_PACKET_HEADER_SIZE) ?
+ (UPLOAD_PATCH_UNIT - MTK_SDIO_PACKET_HEADER_SIZE) : (section_dl_size - cur_len);
+ if (is_mt7902(bdev->chip_id)) {
+ next_len = section_dl_size - sent_len - cur_len;
+ if (next_len > 0 && next_len < 16)
+ sent_len = (sent_len + next_len) / 2;
+ }
+
+ BTMTK_DBG("%s: sent_len = %d, cur_len = %d, delay_count = %d, next_len = %d",
+ __func__, sent_len, cur_len, delay_count, next_len);
+
+ sdio_len = sent_len + MTK_SDIO_PACKET_HEADER_SIZE;
+ memset(image, 0, UPLOAD_PATCH_UNIT);
+ image[0] = (sdio_len & 0x00FF);
+ image[1] = (sdio_len & 0xFF00) >> 8;
+ image[2] = 0;
+ image[3] = 0;
+
+ memcpy(image + MTK_SDIO_PACKET_HEADER_SIZE,
+ fwbuf + section_offset + cur_len,
+ sent_len);
+
+ block_count = sdio_len / SDIO_BLOCK_SIZE;
+ redundant = sdio_len % SDIO_BLOCK_SIZE;
+ if (redundant)
+ sdio_len = (block_count + 1) * SDIO_BLOCK_SIZE;
+
+ ret = btmtk_sdio_writesb(CTDR, image, sdio_len, cif_dev->func);
+ atomic_set(&cif_dev->tx_rdy, 0);
+ cur_len += sent_len;
+
+ if (ret < 0) {
+ BTMTK_ERR("%s: send patch failed, terminate", __func__);
+ goto enable_intr;
+ }
+ }
+ btmtk_sdio_enable_interrupt(1, cif_dev->func);
+
+ BTMTK_INFO("%s: send dl cmd", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, LD_PATCH_CMD_LEN,
+ event, LD_PATCH_EVT_LEN,
+ PATCH_DOWNLOAD_PHASE3_DELAY_TIME,
+ PATCH_DOWNLOAD_PHASE3_RETRY,
+ BTMTK_TX_ACL_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: send wmd dl cmd failed, terminate!", __func__);
+ return ret;
+ }
+
+ BTMTK_INFO("%s: loading rom patch... Done", __func__);
+ return ret;
+
+enable_intr:
+ btmtk_sdio_enable_interrupt(1, cif_dev->func);
+ return ret;
+}
+
+static int btmtk_sdio_register_dev(struct btmtk_sdio_dev *bdev)
+{
+ struct sdio_func *func;
+ u8 u8ReadCRValue = 0;
+ int ret = 0;
+
+ if (!bdev || !bdev->func) {
+ BTMTK_ERR("Error: card or function is NULL!");
+ ret = -EINVAL;
+ goto failed;
+ }
+
+ func = bdev->func;
+
+ sdio_claim_host(func);
+ ret = sdio_enable_func(func);
+ sdio_release_host(func);
+ if (ret) {
+ BTMTK_ERR("sdio_enable_func() failed: ret=%d", ret);
+ ret = -EIO;
+ goto failed;
+ }
+
+ btmtk_sdio_readb(SDIO_CCCR_IENx, &u8ReadCRValue, func);
+ BTMTK_INFO("before claim irq read SDIO_CCCR_IENx %x, func num %d",
+ u8ReadCRValue, func->num);
+
+ sdio_claim_host(func);
+ ret = sdio_claim_irq(func, btmtk_sdio_interrupt);
+ sdio_release_host(func);
+ if (ret) {
+ BTMTK_ERR("sdio_claim_irq failed: ret=%d", ret);
+ ret = -EIO;
+ goto disable_func;
+ }
+
+ BTMTK_INFO("sdio_claim_irq success: ret=%d", ret);
+
+ btmtk_sdio_readb(SDIO_CCCR_IENx, &u8ReadCRValue, func);
+ BTMTK_INFO("after claim irq read SDIO_CCCR_IENx %x", u8ReadCRValue);
+
+ sdio_claim_host(func);
+ ret = sdio_set_block_size(func, SDIO_BLOCK_SIZE);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err("cannot set SDIO block size");
+ ret = -EIO;
+ goto release_irq;
+ }
+
+
+ return 0;
+
+release_irq:
+ sdio_release_irq(func);
+
+disable_func:
+ sdio_disable_func(func);
+
+failed:
+ pr_info("%s fail", __func__);
+ return ret;
+}
+
+static int btmtk_sdio_enable_host_int(struct btmtk_sdio_dev *cif_dev)
+{
+ int ret = 0;
+ u32 read_data = 0;
+
+ if (!cif_dev || !cif_dev->func)
+ return -EINVAL;
+
+ /* workaround for some platform no host clock sometimes */
+
+ ret = btmtk_sdio_readl(CSDIOCSR, &read_data, cif_dev->func);
+ BTMTK_INFO("%s read CSDIOCSR is 0x%X, ret = %d", __func__, read_data, ret);
+ read_data |= 0x4;
+ ret = btmtk_sdio_writel(CSDIOCSR, read_data, cif_dev->func);
+ BTMTK_INFO("%s write CSDIOCSR is 0x%X, ret = %d", __func__, read_data, ret);
+
+ return ret;
+}
+
+static int btmtk_sdio_unregister_dev(struct btmtk_sdio_dev *cif_dev)
+{
+ if (cif_dev && cif_dev->func) {
+ sdio_claim_host(cif_dev->func);
+ sdio_release_irq(cif_dev->func);
+ sdio_disable_func(cif_dev->func);
+ sdio_release_host(cif_dev->func);
+ sdio_set_drvdata(cif_dev->func, NULL);
+ cif_dev->func = NULL;
+ }
+ return 0;
+}
+
+static int btmtk_sdio_set_write_clear(struct btmtk_sdio_dev *cif_dev)
+{
+ u32 u32ReadCRValue = 0;
+ u32 ret = 0;
+
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ if (ret) {
+ BTMTK_ERR("%s read CHCR error", __func__);
+ ret = EINVAL;
+ return ret;
+ }
+
+ u32ReadCRValue |= 0x00000002;
+ btmtk_sdio_writel(CHCR, u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s write CHCR 0x%08X", __func__, u32ReadCRValue);
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X", __func__, u32ReadCRValue);
+ if (u32ReadCRValue&0x00000002)
+ BTMTK_INFO("%s write clear", __func__);
+ else
+ BTMTK_INFO("%s read clear", __func__);
+
+ return ret;
+}
+
+/* bt_tx_wait_for_msg
+ *
+ * Check needing action of current bt status to wake up bt thread
+ *
+ * Arguments:
+ * [IN] bdev - bt driver control strcuture
+ *
+ * Return Value:
+ * return check - 1 for waking up bt thread, 0 otherwise
+ *
+ */
+static u32 btmtk_thread_wait_for_msg(struct btmtk_sdio_dev *cif_dev)
+{
+ u32 ret = 0;
+
+ if (!skb_queue_empty(&cif_dev->tx_queue) && (atomic_read(&cif_dev->tx_rdy))) {
+ BTMTK_DBG("tx queue is not empty");
+ ret |= BTMTK_THREAD_TX;
+ }
+
+ if (atomic_read(&cif_dev->int_count)) {
+ BTMTK_DBG("cif_dev->int_count is %d", atomic_read(&cif_dev->int_count));
+ ret |= BTMTK_THREAD_RX;
+ }
+
+ if (kthread_should_stop()) {
+ BTMTK_DBG("kthread_should_stop");
+ ret |= BTMTK_THREAD_STOP;
+ }
+
+ if (atomic_read(&cif_dev->fw_own_timer_flag) == FW_OWN_TIMER_RUNNING) {
+ BTMTK_DBG("fw own");
+ ret |= BTMTK_THREAD_FW_OWN;
+ }
+
+ return ret;
+}
+
+static int btmtk_tx_pkt(struct btmtk_sdio_dev *cif_dev, struct sk_buff *skb)
+{
+ u8 MultiBluckCount = 0;
+ u8 redundant = 0;
+ int len = 0;
+ int ret = 0;
+
+ BTMTK_DBG("btmtk_tx_pkt");
+
+ cif_dev->sdio_packet[0] = (4 + skb->len) & 0xFF;
+ cif_dev->sdio_packet[1] = ((4 + skb->len) & 0xFF00) >> 8;
+
+ memcpy(cif_dev->sdio_packet + MTK_SDIO_PACKET_HEADER_SIZE, skb->data,
+ skb->len);
+ len = skb->len + MTK_SDIO_PACKET_HEADER_SIZE;
+ BTMTK_DBG_RAW(cif_dev->sdio_packet, len,
+ "%s: sent, len =%d:", __func__, len);
+
+ MultiBluckCount = len / SDIO_BLOCK_SIZE;
+ redundant = len % SDIO_BLOCK_SIZE;
+ if (redundant)
+ len = (MultiBluckCount+1)*SDIO_BLOCK_SIZE;
+
+ atomic_set(&cif_dev->tx_rdy, 0);
+ ret = btmtk_sdio_writesb(CTDR, cif_dev->sdio_packet, len, cif_dev->func);
+ if (ret < 0)
+ BTMTK_ERR("ret = %d", ret);
+ kfree_skb(skb);
+ return ret;
+}
+
+static int btmtk_sdio_interrupt_process(struct btmtk_dev *bdev)
+{
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ int ret = 0;
+ u32 u32ReadCRValue = 0;
+
+ ret = btmtk_sdio_readl(CHISR, &u32ReadCRValue, cif_dev->func);
+#if BTMTK_SDIO_DEBUG
+ rx_debug_save(CHISR_r_1, u32ReadCRValue, NULL);
+#endif
+ BTMTK_DBG("%s CHISR 0x%08x", __func__, u32ReadCRValue);
+
+ if (u32ReadCRValue & FIRMWARE_INT_BIT15) {
+ btmtk_sdio_set_no_fwn_own(cif_dev, 1);
+ btmtk_sdio_writel(PH2DSM0R, PH2DSM0R_DRIVER_OWN, cif_dev->func);
+ }
+
+ if (u32ReadCRValue & FIRMWARE_INT_BIT31) {
+ /* clean tx queue */
+ skb_queue_purge(&cif_dev->tx_queue);
+
+ /* It's read-only bit (WDT interrupt)
+ * Host can't modify it.
+ */
+ ret = btmtk_sdio_readl(CHISR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s CHISR 0x%08x", __func__, u32ReadCRValue);
+ DUMP_TIME_STAMP("notify_chip_reset");
+ btmtk_reset_trigger(bdev);
+ return ret;
+ }
+
+ if (TX_EMPTY & u32ReadCRValue) {
+ ret = btmtk_sdio_writel(CHISR, (TX_EMPTY | TX_COMPLETE_COUNT), cif_dev->func);
+ atomic_set(&cif_dev->tx_rdy, 1);
+ BTMTK_DBG("%s set tx_rdy true", __func__);
+ if (ret < 0)
+ BTMTK_ERR(" %s, ret:%d", __func__, ret);
+#if BTMTK_SDIO_DEBUG
+ tx_empty_cnt++;
+#endif
+ }
+
+ if (RX_DONE & u32ReadCRValue) {
+ ret = btmtk_cif_recv_evt(bdev);
+ BTMTK_DBG("%s recv_evt, ret = %d", __func__, ret);
+ }
+
+ ret = btmtk_sdio_enable_interrupt(1, cif_dev->func);
+ BTMTK_DBG("%s done, ret = %d", __func__, ret);
+ return ret;
+}
+
+
+/*
+ * This function handles the event generated by firmware, rx data
+ * received from firmware, and tx data sent from kernel.
+ */
+static int btmtk_sdio_main_thread(void *data)
+{
+ struct btmtk_dev *bdev = data;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ struct sk_buff *skb;
+ int ret = 0;
+ ulong flags;
+ u32 thread_flag = 0;
+#if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE)
+ struct sched_param param = { .sched_priority = 90 }; /* RR 90 is the same as audio*/
+#endif
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+#if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE)
+ sched_setscheduler(current, SCHED_RR, ¶m);
+#else
+ sched_set_fifo(current);
+#endif
+
+ atomic_set(&cif_dev->sdio_thread.thread_status, 1);
+ BTMTK_INFO("thread_status = %d, btmtk_sdio_main_thread start running...",
+ atomic_read(&cif_dev->sdio_thread.thread_status));
+
+ for (;;) {
+ wait_event_interruptible(cif_dev->sdio_thread.wait_q,
+ (thread_flag = btmtk_thread_wait_for_msg(cif_dev)));
+ if (thread_flag & BTMTK_THREAD_STOP) {
+ BTMTK_WARN("sdio_thread: break from main thread");
+ break;
+ }
+ BTMTK_DBG("btmtk_sdio_main_thread doing...");
+
+ if (thread_flag & BTMTK_THREAD_FW_OWN) {
+ ret = btmtk_sdio_set_fw_own(cif_dev, RETRY_TIMES);
+ if (ret) {
+ BTMTK_ERR("set fw own return fail");
+ btmtk_reset_trigger(bdev);
+ break;
+ }
+ }
+
+ if (thread_flag & (BTMTK_THREAD_TX | BTMTK_THREAD_RX)) {
+ ret = btmtk_sdio_set_driver_own(cif_dev, RETRY_TIMES);
+ if (ret) {
+ BTMTK_ERR("set driver own return fail");
+ btmtk_reset_trigger(bdev);
+ break;
+ }
+ }
+
+ /* Do interrupt */
+ if (thread_flag & BTMTK_THREAD_RX) {
+ BTMTK_DBG("go int");
+ atomic_set(&cif_dev->int_count, 0);
+ if (btmtk_sdio_interrupt_process(bdev)) {
+ btmtk_reset_trigger(bdev);
+ break;
+ }
+ } else {
+ BTMTK_DBG("go tx");
+ }
+
+ if (thread_flag & BTMTK_THREAD_TX) {
+ spin_lock_irqsave(&bdev->txlock, flags);
+ skb = skb_dequeue(&cif_dev->tx_queue);
+ spin_unlock_irqrestore(&bdev->txlock, flags);
+ if (skb) {
+ ret = btmtk_tx_pkt(cif_dev, skb);
+ if (ret) {
+ BTMTK_ERR("tx pkt return fail %d", ret);
+ btmtk_reset_trigger(bdev);
+ break;
+ }
+ }
+ }
+ }
+
+ atomic_set(&cif_dev->sdio_thread.thread_status, 0);
+ BTMTK_WARN("end");
+ return 0;
+}
+
+static void btmtk_sdio_stop_main_thread(struct btmtk_sdio_dev *cif_dev)
+{
+ u8 i = 0;
+
+ if (!IS_ERR(cif_dev->sdio_thread.task) && atomic_read(&cif_dev->sdio_thread.thread_status)) {
+ kthread_stop(cif_dev->sdio_thread.task);
+ wake_up_interruptible(&cif_dev->sdio_thread.wait_q);
+
+ while (atomic_read(&cif_dev->sdio_thread.thread_status) && i < RETRY_TIMES) {
+ BTMTK_INFO("wait btmtk_sdio_main_thread stop");
+ msleep(100);
+ i++;
+ if (i == RETRY_TIMES - 1) {
+ BTMTK_INFO("wait btmtk_sdio_main_thread stop failed");
+ break;
+ }
+ }
+ BTMTK_INFO("btmtk_sdio_stop_main_thread end!");
+ }
+}
+
+static int btmtk_sdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ int err = -1;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ u8 i = 0;
+
+ bdev = sdio_get_drvdata(func);
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ return -ENOMEM;
+ }
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ cif_dev->func = func;
+ cif_dev->bdev = bdev;
+ BTMTK_INFO("%s, func device %p", __func__, func);
+ cif_dev->patched = 0;
+
+ /* it's for L0/L0.5 reset */
+ INIT_WORK(&bdev->reset_waker, btmtk_reset_waker);
+ spin_lock_init(&bdev->txlock);
+ spin_lock_init(&bdev->rxlock);
+
+ if (btmtk_sdio_register_dev(cif_dev) < 0) {
+ BTMTK_ERR("Failed to register BT device!");
+ return -ENODEV;
+ }
+
+ /* Disable the interrupts on the card */
+ btmtk_sdio_enable_host_int(cif_dev);
+ BTMTK_DBG("call btmtk_sdio_enable_host_int done");
+
+ sdio_set_drvdata(func, bdev);
+
+ btmtk_sdio_keep_driver_own(cif_dev, 1);
+
+ /* create tx/rx thread */
+ init_waitqueue_head(&cif_dev->sdio_thread.wait_q);
+ skb_queue_head_init(&cif_dev->tx_queue);
+ atomic_set(&cif_dev->int_count, 0);
+ atomic_set(&cif_dev->tx_rdy, 1);
+ cif_dev->sdio_thread.task = kthread_run(btmtk_sdio_main_thread,
+ bdev, "btmtk_sdio_main_thread");
+ if (IS_ERR(cif_dev->sdio_thread.task)) {
+ BTMTK_DBG("btmtk_sdio_ps failed to start!");
+ err = PTR_ERR(cif_dev->sdio_thread.task);
+ goto unreg_sdio;
+
+ }
+
+ /* Set interrupt output */
+ err = btmtk_sdio_writel(CHIER, FIRMWARE_INT_BIT31 | FIRMWARE_INT_BIT15 |
+ FIRMWARE_INT|TX_FIFO_OVERFLOW |
+ FW_INT_IND_INDICATOR | TX_COMPLETE_COUNT |
+ TX_UNDER_THOLD | TX_EMPTY | RX_DONE, cif_dev->func);
+ if (err) {
+ BTMTK_ERR("Set interrupt output fail(%d)", err);
+ err = -EIO;
+ goto free_thread;
+ }
+
+ /* Enable interrupt output */
+ err = btmtk_sdio_writel(CHLPCR, C_FW_INT_EN_SET, cif_dev->func);
+ if (err) {
+ BTMTK_ERR("enable interrupt output fail(%d)", err);
+ err = -EIO;
+ goto free_thread;
+ }
+
+ /* write clear method */
+ btmtk_sdio_set_write_clear(cif_dev);
+
+ /* old method for chip id
+ * btmtk_sdio_readl(0, &u32ReadCRValue, bdev->func);
+ * BTMTK_INFO("%s read chipid = %x", __func__, u32ReadCRValue);
+ */
+
+ err = btmtk_cif_allocate_memory(cif_dev);
+ if (err < 0) {
+ BTMTK_ERR("[ERR] btmtk_cif_allocate_memory failed!");
+ goto free_thread;
+ }
+
+ /* temp solution for fix when do read chip id, main thread don't start at the time,
+ * then read chip id will be failed. The final solution maybe need move all
+ * cmd to the new tx thread.
+ */
+ while (!atomic_read(&cif_dev->sdio_thread.thread_status) && i < CHECK_THREAD_RETRY_TIMES) {
+ BTMTK_INFO("wait btmtk_sdio_main_thread start");
+ usleep_range(5*1000, 10*1000);
+ i++;
+ if (i == RETRY_TIMES - 1) {
+ BTMTK_WARN("wait btmtk_sdio_main_thread start failed, do chip reset!!!");
+ bdev->bt_cfg.support_dongle_reset = 1;
+ atomic_set(&bmain_info->chip_reset, BTMTK_RESET_DONE);
+ btmtk_reset_trigger(bdev);
+ goto exit;
+ }
+ }
+
+ err = btmtk_main_cif_initialize(bdev, HCI_SDIO);
+ if (err < 0) {
+ if (err == -EIO) {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed, do chip reset!!!");
+ goto exit;
+ } else {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed!");
+ goto free_mem;
+ }
+ }
+
+#if CFG_SUPPORT_HW_DVT
+ /* We don't need to download patch during bring-up stage. */
+ BTMTK_INFO("SKIP downlaod patch");
+#else
+ err = btmtk_load_rom_patch(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk load rom patch failed, do chip reset!!!");
+ goto exit;
+ }
+#endif /* CFG_SUPPORT_HW_DVT */
+
+ /* It's HW workaround for mt7921 */
+ if (is_mt7961(bdev->chip_id))
+ cif_dev->patched = 1;
+
+ err = btmtk_woble_initialize(bdev, &cif_dev->bt_woble);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_main_woble_initialize failed, do chip reset!!!");
+ goto exit;
+ }
+
+ btmtk_buffer_mode_initialize(bdev, &cif_dev->buffer_mode);
+
+#if CFG_SUPPORT_BLUEZ
+ err = btmtk_send_init_cmds(bdev);
+ if (err < 0) {
+ BTMTK_ERR("%s, btmtk_send_init_cmds failed, err = %d", __func__, err);
+ goto free_setting;
+ }
+#endif /* CFG_SUPPORT_BLUEZ */
+
+ err = btmtk_register_hci_device(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_register_hci_device failed!");
+ goto free_setting;
+ }
+
+ btmtk_sdio_writel(0x40, 0x9F1E0000, cif_dev->func);
+
+ goto end;
+
+free_setting:
+ btmtk_woble_uninitialize(&cif_dev->bt_woble);
+ btmtk_main_cif_uninitialize(bdev, HCI_SDIO);
+free_mem:
+ btmtk_cif_free_memory(cif_dev);
+free_thread:
+ btmtk_sdio_stop_main_thread(cif_dev);
+unreg_sdio:
+ btmtk_sdio_unregister_dev(cif_dev);
+end:
+ BTMTK_INFO("%s normal end, ret = %d", __func__, err);
+#if CFG_SUPPORT_HW_DVT
+ /* We don't need to enable low_power faeture during HW bring-up stage. */
+ BTMTK_INFO("Keep driver own during bring-up stage");
+#else
+ btmtk_sdio_keep_driver_own(cif_dev, 0);
+#endif /* CFG_SUPPORT_HW_DVT */
+ btmtk_woble_wake_unlock(bdev);
+
+exit:
+ atomic_set(&bmain_info->chip_reset, BTMTK_RESET_DONE);
+ return 0;
+}
+
+static void btmtk_sdio_disconnect(struct sdio_func *func)
+{
+ struct btmtk_dev *bdev = sdio_get_drvdata(func);
+ struct btmtk_sdio_dev *cif_dev = NULL;
+
+ if (!bdev)
+ return;
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ btmtk_sdio_stop_main_thread(cif_dev);
+ btmtk_main_cif_disconnect_notify(bdev, HCI_SDIO);
+ btmtk_woble_uninitialize(&cif_dev->bt_woble);
+ btmtk_cif_free_memory(cif_dev);
+ btmtk_sdio_unregister_dev(cif_dev);
+ skb_queue_purge(&cif_dev->tx_queue);
+
+}
+
+static int btmtk_cif_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ int ret = -1;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ /* Mediatek Driver Version */
+ BTMTK_INFO("%s: MTK BT Driver Version: %s", __func__, VERSION);
+
+ BTMTK_DBG("vendor=0x%x, device=0x%x, class=%d, fn=%d",
+ id->vendor, id->device, id->class,
+ func->num);
+ DUMP_TIME_STAMP("probe_start");
+
+ /* sdio interface numbers */
+ if (func->num != BTMTK_SDIO_FUNC) {
+ BTMTK_INFO("%s: func num is not match, func_num = %d", __func__, func->num);
+ return -ENODEV;
+ }
+
+ /* Retrieve priv data and set to interface structure */
+ bdev = btmtk_get_dev();
+ if (!bdev) {
+ BTMTK_INFO("%s: bdev is NULL", __func__);
+ return -ENODEV;
+ }
+
+ bdev->intf_dev = &func->dev;
+ bdev->cif_dev = &g_sdio_dev;
+ sdio_set_drvdata(func, bdev);
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_PROBE;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btmtk_sdio_probe(func, id);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ DUMP_TIME_STAMP("probe_end");
+ return ret;
+}
+
+static void btmtk_cif_disconnect(struct sdio_func *func)
+{
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ bdev = sdio_get_drvdata(func);
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_DISCONNECT;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ btmtk_sdio_disconnect(func);
+
+ /* Set End/Error state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+}
+
+#ifdef CONFIG_PM
+static int btmtk_cif_suspend(struct device *dev)
+{
+ int ret = 0;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ int state = BTMTK_STATE_INIT;
+ struct sdio_func *func = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ struct btmtk_woble *bt_woble = NULL;
+ mmc_pm_flag_t pm_flags;
+ struct irq_desc *desc;
+
+ BTMTK_INFO("%s, enter", __func__);
+
+ if (!dev)
+ return 0;
+ func = dev_to_sdio_func(dev);
+ bdev = sdio_get_drvdata(func);
+ if (!bdev)
+ return 0;
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ bt_woble = &cif_dev->bt_woble;
+
+ btmtk_sdio_keep_driver_own(cif_dev, 1);
+
+ if (bdev->suspend_count++) {
+ BTMTK_WARN("Has suspended. suspend_count: %d, end", bdev->suspend_count);
+ return 0;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ /* Retrieve current HIF event state */
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't dos suspend flow!!!", __func__);
+ cif_event = HIF_EVENT_FW_DUMP;
+ } else
+ cif_event = HIF_EVENT_SUSPEND;
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_suspend flow", __func__);
+#else
+ ret = btmtk_woble_suspend(bt_woble);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_suspend return fail %d", __func__, ret);
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ if (bt_woble->wobt_irq != 0 && atomic_read(&(bt_woble->irq_enable_count)) == 0) {
+ /* clear irq data before enable woble irq to avoid DUT wake up
+ * automatically by edge trigger, sync from Jira CONN-50629
+ */
+ desc = irq_to_desc(bt_woble->wobt_irq);
+ if (desc)
+ desc->irq_data.chip->irq_ack(&desc->irq_data);
+ else
+ BTMTK_INFO("%s:can't get desc\n", __func__);
+
+ BTMTK_INFO("%s, enable BT IRQ:%d", __func__, bt_woble->wobt_irq);
+ irq_set_irq_wake(bt_woble->wobt_irq, 1);
+ enable_irq(bt_woble->wobt_irq);
+ atomic_inc(&(bt_woble->irq_enable_count));
+ } else
+ BTMTK_INFO("%s, irq_enable count:%d", __func__, atomic_read(&(bt_woble->irq_enable_count)));
+ }
+
+ pm_flags = sdio_get_host_pm_caps(func);
+ if (!(pm_flags & MMC_PM_KEEP_POWER)) {
+ BTMTK_ERR("%s, %s cannot remain alive while suspended(0x%x)", __func__,
+ sdio_func_id(func), pm_flags);
+ }
+
+ pm_flags = MMC_PM_KEEP_POWER;
+ ret = sdio_set_host_pm_flags(func, pm_flags);
+ if (ret) {
+ BTMTK_ERR("%s, set flag 0x%x err %d", __func__, pm_flags, (int)ret);
+ ret = -ENOSYS;
+ }
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ btmtk_sdio_keep_driver_own(cif_dev, 0);
+ BTMTK_INFO("%s, end. ret = %d", __func__, ret);
+ return ret;
+}
+
+static int btmtk_cif_resume(struct device *dev)
+{
+ u8 ret = 0;
+ struct sdio_func *func = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_woble *bt_woble = NULL;
+
+ BTMTK_INFO("%s, enter", __func__);
+
+#if WAKEUP_BT_IRQ
+ btmtk_sdio_irq_wake_lock_timeout(NULL);
+#endif
+
+ if (!dev)
+ return 0;
+ func = dev_to_sdio_func(dev);
+ bdev = sdio_get_drvdata(func);
+ if (!bdev)
+ return 0;
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ bt_woble = &cif_dev->bt_woble;
+
+ bdev->suspend_count--;
+ if (bdev->suspend_count) {
+ BTMTK_INFO("data->suspend_count %d, return 0", bdev->suspend_count);
+ return 0;
+ }
+
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ if (bt_woble->wobt_irq != 0 && atomic_read(&(bt_woble->irq_enable_count)) == 1) {
+ BTMTK_INFO("disable BT IRQ:%d", bt_woble->wobt_irq);
+ atomic_dec(&(bt_woble->irq_enable_count));
+ disable_irq_nosync(bt_woble->wobt_irq);
+ } else
+ BTMTK_INFO("irq_enable count:%d", atomic_read(&(bt_woble->irq_enable_count)));
+ }
+
+ cif_state = &bdev->cif_state[HIF_EVENT_RESUME];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_resume flow", __func__);
+#else
+ ret = btmtk_woble_resume(bt_woble);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_resume return fail %d", __func__, ret);
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ BTMTK_INFO("end");
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static int btmtk_sdio_poll_subsys_done(struct btmtk_sdio_dev *cif_dev)
+{
+ u32 u32ReadCRValue = 0;
+ int retry = 100;
+
+/* btmtk_sdio_writel(0x30, 0xFD, cif_dev->func);
+ * BTMTK_INFO("%s write 0x30 = 0xFD, retry = %d", __func__, retry);
+ */
+ while (retry-- > 0) {
+/* btmtk_sdio_readl(0x2c, &u32ReadCRValue, cif_dev->func);
+ * BTMTK_INFO("%s read 0x2c = 0x%08X, retry = %d", __func__, u32ReadCRValue, retry);
+ * btmtk_sdio_readl(CHLPCR, &u32ReadCRValue, cif_dev->func);
+ * BTMTK_INFO("%s read CHLPCR 0x%08X, retry = %d", __func__, u32ReadCRValue, retry);
+ * btmtk_sdio_readl(SWPCDBGR, &u32ReadCRValue, cif_dev->func);
+ * BTMTK_INFO("%s read SWPCDBGR 0x%08X, retry = %d", __func__, u32ReadCRValue, retry);
+ */
+ btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X, retry = %d", __func__, u32ReadCRValue, retry);
+ if (u32ReadCRValue & (0x1 << 8))
+ return 0;
+ msleep(20);
+ }
+
+ return -1;
+}
+
+static int btmtk_sdio_subsys_reset(struct btmtk_dev *bdev)
+{
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+ u32 u32ReadCRValue = 0;
+ int retry = RETRY_TIMES;
+ u32 ret = 0;
+ atomic_t subreset_retry;
+
+ atomic_set(&subreset_retry, 0);
+reset_retry:
+ if (atomic_read(&subreset_retry) == BTMTK_MAX_SUBSYS_RESET_COUNT) {
+ BTMTK_ERR("%s, reset_retry == 3", __func__);
+ ret = -EIO;
+ goto free_thread;
+ }
+
+ do {
+ /* After WDT, CHLPCR maybe can't show driver/fw own status
+ * BT SW should check PD2HRM0R bit 0
+ * 1: Driver own. 0: FW own
+ */
+ btmtk_sdio_keep_driver_own(cif_dev, 1);
+ if (!is_mt7961(bdev->chip_id))
+ break;
+ ret = btmtk_sdio_readl(PD2HRM0R, &u32ReadCRValue, cif_dev->func);
+ msleep(DELAY_TIMES);
+ retry--;
+ } while (((u32ReadCRValue & PD2HRM0R_DRIVER_OWN) != PD2HRM0R_DRIVER_OWN)
+ && retry > 0);
+
+ BTMTK_INFO("%s read PD2HRM0R 0x%08X", __func__, u32ReadCRValue);
+
+ cif_dev->patched = 0;
+ /*
+ * If trigger subsys reset by userspace, we should clean queue before
+ * subsys reset.
+ */
+ skb_queue_purge(&cif_dev->tx_queue);
+ btmtk_sdio_set_wifi_driver_own(1);
+
+ /* write CHCR[3] 0 */
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X", __func__, u32ReadCRValue);
+ u32ReadCRValue &= 0xFFFFFFF7;
+ BTMTK_INFO("%s write CHCR 0x%08X", __func__, u32ReadCRValue);
+ btmtk_sdio_writel(CHCR, u32ReadCRValue, cif_dev->func);
+
+ /* write CHCR[3] to 1 */
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X", __func__, u32ReadCRValue);
+ u32ReadCRValue |= 0x00000008;
+ BTMTK_INFO("%s write CHCR 0x%08X", __func__, u32ReadCRValue);
+ btmtk_sdio_writel(CHCR, u32ReadCRValue, cif_dev->func);
+
+ /* write CHCR[5] to 0 */
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X", __func__, u32ReadCRValue);
+ u32ReadCRValue &= 0xFFFFFFDF;
+ BTMTK_INFO("%s write CHCR 0x%08X", __func__, u32ReadCRValue);
+ btmtk_sdio_writel(CHCR, u32ReadCRValue, cif_dev->func);
+
+ /* write CHCR[5] to 1 */
+ ret = btmtk_sdio_readl(CHCR, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read CHCR 0x%08X", __func__, u32ReadCRValue);
+ u32ReadCRValue |= 0x00000020;
+ BTMTK_INFO("%s write CHCR 0x%08X", __func__, u32ReadCRValue);
+ btmtk_sdio_writel(CHCR, u32ReadCRValue, cif_dev->func);
+
+ /* Poll subsys reset done */
+ if (btmtk_sdio_poll_subsys_done(cif_dev)) {
+ ret = -EIO;
+ BTMTK_ERR("%s btmtk_sdio_poll_subsys_done fail", __func__);
+ goto free_thread;
+ }
+
+ /* if thread stopped, we need to create a new thread before subsys reset */
+ if (!atomic_read(&cif_dev->sdio_thread.thread_status)) {
+ atomic_set(&cif_dev->tx_rdy, 1);
+ atomic_set(&cif_dev->int_count, 0);
+ cif_dev->sdio_thread.task = kthread_run(btmtk_sdio_main_thread,
+ bdev, "btmtk_sdio_main_thread");
+ if (IS_ERR(cif_dev->sdio_thread.task)) {
+ BTMTK_ERR("btmtk_sdio_main_thread failed to start!");
+ ret = PTR_ERR(cif_dev->sdio_thread.task);
+ goto exit;
+ }
+ }
+
+ /* make sure sdio enable func */
+ sdio_claim_host(cif_dev->func);
+ BTMTK_INFO("%s sdio_enable_func", __func__);
+ sdio_enable_func(cif_dev->func);
+ sdio_release_host(cif_dev->func);
+
+ /* Do-init cr */
+ /* Disable the interrupts on the card */
+ btmtk_sdio_enable_host_int(cif_dev);
+ BTMTK_DBG("call btmtk_sdio_enable_host_int done");
+
+ atomic_set(&cif_dev->tx_rdy, 1);
+ atomic_set(&cif_dev->int_count, 0);
+
+ /* Set interrupt output */
+ ret = btmtk_sdio_writel(CHIER, FIRMWARE_INT_BIT31 | FIRMWARE_INT|TX_FIFO_OVERFLOW |
+ FW_INT_IND_INDICATOR | TX_COMPLETE_COUNT |
+ TX_UNDER_THOLD | TX_EMPTY | RX_DONE, cif_dev->func);
+ if (ret) {
+ BTMTK_ERR("Set interrupt output fail(%d)", ret);
+ ret = -EIO;
+ goto free_thread;
+ }
+
+ /* Enable interrupt output */
+ ret = btmtk_sdio_writel(CHLPCR, C_FW_INT_EN_SET, cif_dev->func);
+ if (ret) {
+ BTMTK_ERR("enable interrupt output fail(%d)", ret);
+ ret = -EIO;
+ goto free_thread;
+ }
+
+ /* Adopt write clear method */
+ btmtk_sdio_set_write_clear(cif_dev);
+
+ ret = btmtk_sdio_readl(0, &u32ReadCRValue, cif_dev->func);
+ BTMTK_INFO("%s read chipid = %x", __func__, u32ReadCRValue);
+ if (ret) {
+ BTMTK_ERR("%s, read chipid fail(%d)", __func__, ret);
+ ret = -EIO;
+ goto free_thread;
+ }
+
+ if (u32ReadCRValue != (0xf00000 | bdev->chip_id)) {
+ BTMTK_ERR("%s, reset retry, u32ReadCRValue != 0x%06x", __func__, (0xf00000 | bdev->chip_id));
+ atomic_inc(&subreset_retry);
+ goto reset_retry;
+ }
+
+ ret = btmtk_cap_init(bdev);
+ if (ret < 0) {
+ BTMTK_ERR("btmtk init failed!");
+ atomic_inc(&subreset_retry);
+ goto reset_retry;
+ }
+ goto exit;
+
+free_thread:
+ btmtk_sdio_stop_main_thread(cif_dev);
+
+exit:
+ return ret;
+}
+
+int btmtk_sdio_whole_reset(struct btmtk_dev *bdev)
+{
+ int ret = -1;
+ int cur = 0;
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ struct mmc_card *card = NULL;
+ struct mmc_host *host = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ if (bdev == NULL || bdev->cif_dev == NULL)
+ cif_dev = &g_sdio_dev;
+ else
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ if (cif_dev->func == NULL) {
+ BTMTK_ERR("g_sdio_dev.func is NULL");
+ return ret;
+ }
+
+ card = cif_dev->func->card;
+ if ((card == NULL) || (card->host == NULL)) {
+ BTMTK_ERR("mmc structs are NULL");
+ return ret;
+ }
+
+ cur = atomic_cmpxchg(&bmain_info->chip_reset, BTMTK_RESET_DONE, BTMTK_RESET_DOING);
+ if (cur == BTMTK_RESET_DOING) {
+ BTMTK_INFO("%s: reset in progress, return", __func__);
+ return ret;
+ }
+
+ host = card->host;
+ if (host->rescan_entered != 0) {
+ host->rescan_entered = 0;
+ BTMTK_INFO("%s, set mmc_host rescan to 0", __func__);
+ }
+ cif_dev->patched = 0;
+ btmtk_sdio_set_wifi_driver_own(0);
+
+ BTMTK_INFO("%s, mmc_remove_host", __func__);
+ mmc_remove_host(host);
+
+ /* Replace hooked SDIO driver probe to new API;
+ * 1. It will be new kthread(state) after mmc_add_host;
+ * 2. Extend flexibility to notify us that HW reset was triggered,
+ * more flexiable on reviving in exchanging old/new kthread(state).
+ */
+ BTMTK_INFO("%s, mmc_add_host", __func__);
+ ret = mmc_add_host(host);
+
+ BTMTK_INFO("%s, mmc_add_host return %d", __func__, ret);
+ return ret;
+}
+EXPORT_SYMBOL(btmtk_sdio_whole_reset);
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops btmtk_sdio_pm_ops = {
+ .suspend = btmtk_cif_suspend,
+ .resume = btmtk_cif_resume,
+};
+#endif
+
+static struct sdio_driver btmtk_sdio_driver = {
+ .name = "btsdio",
+ .id_table = btmtk_sdio_tabls,
+ .probe = btmtk_cif_probe,
+ .remove = btmtk_cif_disconnect,
+ .drv = {
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &btmtk_sdio_pm_ops,
+#endif
+ }
+};
+
+static int sdio_register(void)
+{
+ BTMTK_INFO("%s", __func__);
+
+ if (sdio_register_driver(&btmtk_sdio_driver) != 0)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int sdio_deregister(void)
+{
+ BTMTK_INFO("%s", __func__);
+ sdio_unregister_driver(&btmtk_sdio_driver);
+ return 0;
+}
+
+static void btmtk_sdio_chip_reset_notify(struct btmtk_dev *bdev)
+{
+ struct btmtk_sdio_dev *cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ if (cif_dev == NULL) {
+ BTMTK_INFO("%s, cif_dev is NULL", __func__);
+ return;
+ }
+ btmtk_sdio_keep_driver_own(cif_dev, 0);
+ /* It's HW workaround for mt7921 */
+ if (is_mt7961(bdev->chip_id))
+ cif_dev->patched = 1;
+ atomic_set(&cif_dev->tx_rdy, 1);
+
+ btmtk_sdio_set_wifi_driver_own(0);
+}
+
+int btmtk_cif_register(void)
+{
+ int retval = 0;
+ struct hif_hook_ptr hook;
+
+ BTMTK_INFO("%s", __func__);
+
+ memset(&hook, 0, sizeof(hook));
+ hook.open = btmtk_sdio_open;
+ hook.close = btmtk_sdio_close;
+ hook.reg_read = btmtk_sdio_read_register;
+ hook.reg_write = btmtk_sdio_write_register;
+ hook.send_cmd = btmtk_sdio_send_cmd;
+ hook.send_and_recv = btmtk_sdio_send_and_recv;
+ hook.event_filter = btmtk_sdio_event_filter;
+ hook.subsys_reset = btmtk_sdio_subsys_reset;
+ hook.whole_reset = btmtk_sdio_whole_reset;
+ hook.chip_reset_notify = btmtk_sdio_chip_reset_notify;
+ hook.cif_mutex_lock = btmtk_sdio_cif_mutex_lock;
+ hook.cif_mutex_unlock = btmtk_sdio_cif_mutex_unlock;
+ hook.open_done = btmtk_sdio_open_done;
+ hook.dl_dma = btmtk_sdio_load_fw_patch_using_dma;
+ hook.dump_debug_sop = btmtk_sdio_dump_debug_sop;
+ btmtk_reg_hif_hook(&hook);
+
+ btmtk_sdio_create_fw_own_timer(&g_sdio_dev);
+ retval = sdio_register();
+ if (retval)
+ BTMTK_ERR("*** SDIO registration fail(%d)! ***", retval);
+ else
+ BTMTK_INFO("%s, SDIO registration success!", __func__);
+ return retval;
+}
+
+int btmtk_cif_deregister(void)
+{
+ BTMTK_INFO("%s", __func__);
+
+ sdio_deregister();
+ btmtk_sdio_delete_fw_own_timer(&g_sdio_dev);
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+}
+
diff --git a/uart/btmtkserdev.c b/uart/btmtkserdev.c
new file mode 100755
index 0000000..cce7819
--- /dev/null
+++ b/uart/btmtkserdev.c
@@ -0,0 +1,628 @@
+/*
+ *
+ * Generic Bluetooth USB driver
+ *
+ * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include "btmtk_define.h"
+#include "btmtk_uart_serdev.h"
+#include "btmtk_main.h"
+
+static char event_need_compare[EVENT_COMPARE_SIZE] = {0};
+static char event_need_compare_len;
+static char event_compare_status;
+
+static DEFINE_MUTEX(btmtk_uart_ops_mutex);
+#define UART_OPS_MUTEX_LOCK() mutex_lock(&btmtk_uart_ops_mutex)
+#define UART_OPS_MUTEX_UNLOCK() mutex_unlock(&btmtk_uart_ops_mutex)
+static struct btmtk_uart_dev g_uart_dev;
+
+static void btmtk_uart_write_wakeup(struct serdev_device *serdev)
+{
+ //struct btmtk_dev *bdev = serdev_device_get_drvdata(serdev);
+
+ /* call
+ schedule_work(&bdev->tx_work); or serdev_device_write_buf(serdev, skb->data, skb->len);
+ */
+}
+
+
+static int btmtk_uart_receive_buf(struct serdev_device *serdev, const u8 *data,
+ size_t count)
+{
+ struct btmtk_dev *bdev = serdev_device_get_drvdata(serdev);
+ //int err;
+
+ /* call btmtk_recv */
+ bdev->hdev->stat.byte_rx += count;
+
+ return count;
+}
+
+static void btmtk_uart_cif_mutex_lock(struct btmtk_dev *bdev)
+{
+ UART_OPS_MUTEX_LOCK();
+}
+
+static void btmtk_uart_cif_mutex_unlock(struct btmtk_dev *bdev)
+{
+ UART_OPS_MUTEX_UNLOCK();
+}
+
+static int btmtk_uart_read_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ int ret;
+ u8 cmd[READ_REGISTER_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x0C,
+ 0x01, 0x08, 0x08, 0x00,
+ 0x02, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00};
+
+ u8 event[READ_REGISTER_EVT_HDR_LEN] = {0x04, 0xE4, 0x10, 0x02,
+ 0x08, 0x0C, 0x00, 0x00,
+ 0x00, 0x00, 0x01};
+
+ BTMTK_INFO("%s: read cr %x", __func__, reg);
+
+ memcpy(&cmd[MCU_ADDRESS_OFFSET_CMD], ®, sizeof(reg));
+
+ ret = btmtk_main_send_cmd(bdev, cmd, READ_REGISTER_CMD_LEN, event, READ_REGISTER_EVT_HDR_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ memcpy(val, bdev->io_buf + MCU_ADDRESS_OFFSET_EVT - HCI_TYPE_SIZE, sizeof(u32));
+ *val = le32_to_cpu(*val);
+
+ BTMTK_INFO("%s: reg=%x, value=0x%08x", __func__, reg, *val);
+
+ return 0;
+}
+
+int btmtk_cif_send_calibration(struct btmtk_dev *bdev)
+{
+ return 0;
+}
+
+static int btmtk_cif_allocate_memory(struct btmtk_uart_dev *cif_dev)
+{
+ if (cif_dev->transfer_buf == NULL) {
+ cif_dev->transfer_buf = kzalloc(MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->transfer_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->transfer_buf)", __func__);
+ return -1;
+ }
+ }
+
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+}
+
+static void btmtk_cif_free_memory(struct btmtk_uart_dev *cif_dev)
+{
+ kfree(cif_dev->transfer_buf);
+ cif_dev->transfer_buf = NULL;
+
+ BTMTK_INFO("%s: Success", __func__);
+}
+
+static int btmtk_uart_open(struct hci_dev *hdev)
+{
+ //struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ //struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s enter!", __func__);
+
+ return 0;
+}
+
+static int btmtk_uart_close(struct hci_dev *hdev)
+{
+ //struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+
+ BTMTK_INFO("%s enter!", __func__);
+ return 0;
+}
+
+int btmtk_uart_send_cmd(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type)
+{
+
+ return 0;
+}
+
+static int btmtk_cif_recv_evt(struct btmtk_dev *bdev)
+{
+
+ return 0;
+}
+
+int btmtk_uart_event_filter(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ const u8 read_address_event[READ_ADDRESS_EVT_HDR_LEN] = { 0x4, 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00 };
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE &&
+ skb->len >= event_need_compare_len) {
+ if (memcmp(skb->data, &read_address_event[1], READ_ADDRESS_EVT_HDR_LEN - 1) == 0
+ && (skb->len == (READ_ADDRESS_EVT_HDR_LEN - 1 + BD_ADDRESS_SIZE))) {
+ memcpy(bdev->bdaddr, &skb->data[READ_ADDRESS_EVT_PAYLOAD_OFFSET - 1], BD_ADDRESS_SIZE);
+ BTMTK_INFO("%s: GET BDADDR = "MACSTR, __func__, MAC2STR(bdev->bdaddr));
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ } else if (memcmp(skb->data, event_need_compare,
+ event_need_compare_len) == 0) {
+ /* if it is wobx debug event, just print in kernel log, drop it
+ * by driver, don't send to stack
+ */
+ if (skb->data[0] == WOBLE_DEBUG_EVT_TYPE)
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: wobx debug log:", __func__);
+
+ /* If driver need to check result from skb, it can get from io_buf */
+ /* Such as chip_id, fw_version, etc. */
+ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
+ memcpy(bdev->io_buf, skb->data, skb->len);
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ BTMTK_DBG("%s, compare success", __func__);
+ } else {
+ BTMTK_INFO("%s compare fail", __func__);
+ BTMTK_INFO_RAW(event_need_compare, event_need_compare_len,
+ "%s: event_need_compare:", __func__);
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: skb->data:", __func__);
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int btmtk_uart_send_and_recv(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type)
+{
+ unsigned long comp_event_timo = 0, start_time = 0;
+ int ret = -1;
+
+ if (event) {
+ if (event_len > EVENT_COMPARE_SIZE) {
+ BTMTK_ERR("%s, event_len (%d) > EVENT_COMPARE_SIZE(%d), error",
+ __func__, event_len, EVENT_COMPARE_SIZE);
+ ret = -1;
+ goto exit;
+ }
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE;
+ memcpy(event_need_compare, event + 1, event_len - 1);
+ event_need_compare_len = event_len - 1;
+
+ start_time = jiffies;
+ /* check hci event /wmt event for uart/UART interface, check hci
+ * event for USB interface
+ */
+ comp_event_timo = jiffies + msecs_to_jiffies(WOBLE_COMP_EVENT_TIMO);
+ BTMTK_DBG("event_need_compare_len %d, event_compare_status %d",
+ event_need_compare_len, event_compare_status);
+ } else {
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ }
+
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send, len = %d", __func__, skb->len);
+
+ ret = btmtk_uart_send_cmd(bdev, skb, delay, retry, pkt_type);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_uart_send_cmd failed!!", __func__);
+ goto fw_assert;
+ }
+
+ do {
+ /* check if event_compare_success */
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS) {
+ ret = 0;
+ break;
+ }
+ usleep_range(10, 100);
+ } while (time_before(jiffies, comp_event_timo));
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NOTHING_NEED_COMPARE;
+ goto exit;
+fw_assert:
+ btmtk_send_assert_cmd(bdev);
+exit:
+ return ret;
+}
+
+static int btmtk_uart_load_fw_patch_using_dma(struct btmtk_dev *bdev, u8 *image,
+ u8 *fwbuf, int section_dl_size, int section_offset)
+{
+ return 0;
+}
+
+static int btmtk_uart_subsys_reset(struct btmtk_dev *bdev)
+{
+ //struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ /* Process GPIO for subsys reset pin */
+ return 0;
+}
+
+static int btmtk_uart_whole_reset(struct btmtk_dev *bdev)
+{
+ int ret = -1;
+ /* Process GPIO for whole chip reset pin */
+ /* if wifi use pcie interface, it will cause system reboot. */
+ return ret;
+}
+
+static int btmtk_uart_probe(struct serdev_device *serdev)
+{
+ int err = -1;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ bdev = serdev_device_get_drvdata(serdev);
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ return -ENOMEM;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ cif_dev->serdev = serdev;
+
+ /* if (btmtk_uart_register_dev(cif_dev) < 0) {
+ BTMTK_ERR("Failed to register BT device!");
+ return -ENODEV;
+ } */
+
+ err = btmtk_cif_allocate_memory(cif_dev);
+ if (err < 0) {
+ BTMTK_ERR("[ERR] btmtk_cif_allocate_memory failed!");
+ goto end;
+ }
+
+ err = btmtk_main_cif_initialize(bdev, HCI_UART);
+ if (err < 0) {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed!");
+ goto free_mem;
+ }
+
+ err = btmtk_load_rom_patch(bdev);
+ if (err < 0) {
+ BT_ERR("btmtk load rom patch failed!");
+ goto deinit;
+ }
+
+ err = btmtk_main_woble_initialize(bdev);
+ if (err < 0) {
+ BT_ERR("btmtk_main_woble_initialize failed!");
+ goto free_setting;
+ }
+
+ err = btmtk_register_hci_device(bdev);
+ if (err < 0) {
+ BT_ERR("btmtk_register_hci_device failed!");
+ goto free_setting;
+ }
+
+ goto end;
+
+free_setting:
+ btmtk_free_setting_file(bdev);
+deinit:
+ btmtk_main_cif_uninitialize(bdev, HCI_USB);
+free_mem:
+ btmtk_cif_free_memory(cif_dev);
+//unreg_uart:
+ //btmtk_uart_unregister_dev(cif_dev);
+end:
+ BTMTK_INFO("%s normal end, ret = %d", __func__, err);
+
+ return 0;
+}
+
+static void btmtk_uart_disconnect(struct serdev_device *serdev)
+{
+ struct btmtk_dev *bdev = serdev_device_get_drvdata(serdev);
+
+ if (!bdev)
+ return;
+
+ btmtk_cif_free_memory(bdev->cif_dev);
+ //btmtk_uart_unregister_dev(bdev->cif_dev);
+
+ btmtk_main_cif_disconnect_notify(bdev, HCI_UART);
+}
+
+static int btmtk_cif_probe(struct serdev_device *serdev)
+{
+ int ret = -1;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ /* Mediatek Driver Version */
+ BTMTK_INFO("%s: MTK BT Driver Version : %s", __func__, VERSION);
+
+ /* Retrieve priv data and set to interface structure */
+ bdev = btmtk_get_dev();
+ bdev->cif_dev = &g_uart_dev;
+ serdev_device_set_drvdata(serdev, bdev);
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_PROBE;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btmtk_uart_probe(serdev);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ return ret;
+}
+
+static void btmtk_cif_disconnect(struct serdev_device *serdev)
+{
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ bdev = serdev_device_get_drvdata(serdev);
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_DISCONNECT;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ btmtk_uart_cif_mutex_lock(bdev);
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ btmtk_uart_disconnect(serdev);
+
+ /* Set End/Error state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ btmtk_uart_cif_mutex_unlock(bdev);
+}
+
+#ifdef CONFIG_PM
+static int btmtk_cif_suspend(struct device *dev)
+{
+ int ret = 0;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ int state = BTMTK_STATE_INIT;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ struct serdev_device *serdev = NULL;
+
+ BTMTK_INFO("%s, enter", __func__);
+
+ if (!dev)
+ return 0;
+ serdev = to_serdev_device(dev);
+ if (!serdev)
+ return 0;
+ bdev = serdev_device_get_drvdata(serdev);
+ if (!bdev)
+ return 0;
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ state = btmtk_get_chip_state(bdev);
+ /* Retrieve current HIF event state */
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't dos suspend flow!!!", __func__);
+ cif_event = HIF_EVENT_FW_DUMP;
+ } else {
+ cif_event = HIF_EVENT_SUSPEND;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if CFG_SUPPORT_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_suspend flow", __func__);
+#else
+ ret = btmtk_woble_suspend(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_suspend return fail %d", __func__, ret);
+#endif
+
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ if (bdev->wobt_irq != 0 && atomic_read(&(bdev->irq_enable_count)) == 0) {
+ BTMTK_INFO("enable BT IRQ:%d", bdev->wobt_irq);
+ irq_set_irq_wake(bdev->wobt_irq, 1);
+ enable_irq(bdev->wobt_irq);
+ atomic_inc(&(bdev->irq_enable_count));
+ } else {
+ BTMTK_INFO("irq_enable count:%d", atomic_read(&(bdev->irq_enable_count)));
+ }
+ }
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ BTMTK_INFO("%s, end. ret = %d", __func__, ret);
+ return ret;
+}
+
+static int btmtk_cif_resume(struct device *dev)
+{
+ u8 ret = 0;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct serdev_device *serdev = NULL;
+
+ BTMTK_INFO("%s, enter", __func__);
+
+ if (!dev)
+ return 0;
+ serdev = to_serdev_device(dev);
+ if (!serdev)
+ return 0;
+ bdev = serdev_device_get_drvdata(serdev);
+ if (!bdev)
+ return 0;
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ bdev->suspend_count--;
+ if (bdev->suspend_count) {
+ BTMTK_INFO("data->suspend_count %d, return 0", bdev->suspend_count);
+ return 0;
+ }
+
+ if (bdev->bt_cfg.support_woble_by_eint) {
+ if (bdev->wobt_irq != 0 && atomic_read(&(bdev->irq_enable_count)) == 1) {
+ BTMTK_INFO("disable BT IRQ:%d", bdev->wobt_irq);
+ atomic_dec(&(bdev->irq_enable_count));
+ disable_irq_nosync(bdev->wobt_irq);
+ } else {
+ BTMTK_INFO("irq_enable count:%d", atomic_read(&(bdev->irq_enable_count)));
+ }
+ }
+
+ cif_state = &bdev->cif_state[HIF_EVENT_RESUME];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if CFG_SUPPORT_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_resume flow", __func__);
+#else
+ ret = btmtk_woble_resume(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_resume return fail %d", __func__, ret);
+#endif
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ BTMTK_INFO("end");
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+
+static const struct serdev_device_ops btmtkuart_client_ops = {
+ .receive_buf = btmtk_uart_receive_buf,
+ .write_wakeup = btmtk_uart_write_wakeup,
+};
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops btmtk_uart_pm_ops = {
+ .suspend = btmtk_cif_suspend,
+ .resume = btmtk_cif_resume,
+};
+#endif
+
+static const struct of_device_id mtk_of_match_table[] = {
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, mtk_of_match_table);
+
+static struct serdev_device_driver btmtk_uart_driver = {
+ .probe = btmtk_cif_probe,
+ .remove = btmtk_cif_disconnect,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "btuart",
+ .pm = &btmtk_uart_pm_ops,
+ .of_match_table = of_match_ptr(mtk_of_match_table),
+ }
+};
+
+//module_serdev_device_driver(btmtk_uart_driver);
+
+static int uart_register(void)
+{
+ BTMTK_INFO("%s", __func__);
+ serdev_device_driver_register(&btmtk_uart_driver);
+ return 0;
+}
+
+static int uart_deregister(void)
+{
+ BTMTK_INFO("%s", __func__);
+ serdev_device_driver_unregister(&btmtk_uart_driver);
+ return 0;
+}
+
+static void btmtk_uart_chip_reset_notify(struct btmtk_dev *bdev)
+{
+ //struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+}
+
+int btmtk_cif_register(void)
+{
+ struct hif_hook_ptr hook;
+
+ BTMTK_INFO("%s", __func__);
+
+ memset(&hook, 0, sizeof(hook));
+ hook.open = btmtk_uart_open;
+ hook.close = btmtk_uart_close;
+ hook.reg_read = btmtk_uart_read_register;
+ hook.send_cmd = btmtk_uart_send_cmd;
+ hook.send_and_recv = btmtk_uart_send_and_recv;
+ hook.event_filter = btmtk_uart_event_filter;
+ hook.subsys_reset = btmtk_uart_subsys_reset;
+ hook.whole_reset = btmtk_uart_whole_reset;
+ hook.chip_reset_notify = btmtk_uart_chip_reset_notify;
+ hook.cif_mutex_lock = btmtk_uart_cif_mutex_lock;
+ hook.cif_mutex_unlock = btmtk_uart_cif_mutex_unlock;
+ hook.dl_dma = btmtk_uart_load_fw_patch_using_dma;
+ btmtk_reg_hif_hook(&hook);
+
+ uart_register();
+ return 0;
+}
+
+int btmtk_cif_deregister(void)
+{
+ BT_INFO("%s", __func__);
+ uart_deregister();
+ BT_INFO("%s: Done", __func__);
+ return 0;
+}
+
diff --git a/uart/btmtktty.c b/uart/btmtktty.c
new file mode 100755
index 0000000..054555f
--- /dev/null
+++ b/uart/btmtktty.c
@@ -0,0 +1,2369 @@
+/*
+ * Copyright (c) 2016,2017 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ */
+#include <linux/version.h>
+#if (KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE)
+#include <linux/sched.h>
+#else
+#include <uapi/linux/sched/types.h>
+#endif
+
+#include "btmtk_define.h"
+#include "btmtk_uart_tty.h"
+#include "btmtk_main.h"
+//#include "connv3.h"
+#if (USE_DEVICE_NODE == 1)
+#include "btmtk_proj_sp.h"
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include "connv3_debug_utility.h"
+#include "connv3_mcu_log.h"
+#include "btmtk_fw_log.h"
+#include "connv3.h"
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+/* uarthub API */
+extern int mtk8250_uart_hub_enable_bypass_mode(int bypass);
+extern int mtk8250_uart_hub_is_ready(void);
+extern int mtk8250_uart_hub_set_request(void);
+extern int mtk8250_uart_hub_clear_request(void);
+extern int mtk8250_uart_hub_fifo_ctrl(int ctrl);
+extern int mtk8250_uart_hub_dump_with_tag(const char *tag);
+#endif
+
+#endif
+
+#define LOG TRUE
+
+/*============================================================================*/
+/* Function Prototype */
+/*============================================================================*/
+int btmtk_cif_send_cmd(struct btmtk_dev *bdev, const uint8_t *cmd,
+ const int cmd_len, int retry, int delay);
+static int btmtk_tx_thread_exit(struct btmtk_uart_dev *cif_dev);
+static int btmtk_tx_thread_start(struct btmtk_dev *bdev);
+static int btmtk_uart_tx_thread(void *data);
+static int btmtk_uart_fw_own(struct btmtk_dev *bdev);
+static int btmtk_uart_driver_own(struct btmtk_dev *bdev);
+
+
+/*============================================================================*/
+/* Global variable */
+/*============================================================================*/
+static DECLARE_WAIT_QUEUE_HEAD(tx_wait_q);
+static DECLARE_WAIT_QUEUE_HEAD(drv_own_wait_q);
+static DECLARE_WAIT_QUEUE_HEAD(fw_to_md_wait_q);
+static DEFINE_MUTEX(btmtk_uart_ops_mutex);
+#define UART_OPS_MUTEX_LOCK() mutex_lock(&btmtk_uart_ops_mutex)
+#define UART_OPS_MUTEX_UNLOCK() mutex_unlock(&btmtk_uart_ops_mutex)
+static DEFINE_MUTEX(btmtk_uart_own_mutex);
+#define UART_OWN_MUTEX_LOCK() mutex_lock(&btmtk_uart_own_mutex)
+#define UART_OWN_MUTEX_UNLOCK() mutex_unlock(&btmtk_uart_own_mutex)
+
+static char event_need_compare[EVENT_COMPARE_SIZE] = {0};
+static char event_need_compare_len;
+static char event_compare_status;
+static struct tty_struct *g_tty;
+static struct tty_ldisc_ops btmtk_uart_ldisc;
+
+
+#if (USE_DEVICE_NODE == 1)
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+static int btmtk_wakeup_uarthub(void) {
+ int ready_retry = 50, ret = 0;
+
+ /* Set TX,RX request */
+ ret = mtk8250_uart_hub_set_request();
+ if (ret) {
+ BTMTK_ERR("%s mtk8250_uart_hub_set_request fail ret[%d]", __func__, ret);
+ return -1;
+ }
+
+ /* Polling UARTHUB is ready state */
+ while (mtk8250_uart_hub_is_ready() <= 0 && --ready_retry) {
+ BTMTK_WARN("%s ready_retry[%d]", __func__, ready_retry);
+ usleep_range(1000, 1100);
+ }
+
+ if (ready_retry <= 0) {
+ ret = mtk8250_uart_hub_dump_with_tag("BT driver own");
+ BTMTK_ERR("%s mtk8250_uart_hub_dump_with_tag ready_retry[%d] ret[%d]", __func__, ready_retry, ret);
+ return -1;
+ }
+ return 0;
+}
+#endif // (CONFIG_MTK_UARTHUB)
+#endif //(USE_DEVICE_NODE == 1)
+
+#if (SLEEP_ENABLE == 1)
+
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+static void btmtk_fw_own_timer(unsigned long arg)
+{
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)arg;
+
+ if (atomic_read(&cif_dev->fw_own_timer_flag)) {
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_RUNNING);
+ wake_up_interruptible(&tx_wait_q);
+ } else
+ BTMTK_WARN("%s: not create yet", __func__);
+}
+#else
+static void btmtk_fw_own_timer(struct timer_list *timer)
+{
+ struct btmtk_uart_dev *cif_dev = from_timer(cif_dev, timer, fw_own_timer);
+
+ if (atomic_read(&cif_dev->fw_own_timer_flag)) {
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_RUNNING);
+ wake_up_interruptible(&tx_wait_q);
+ } else
+ BTMTK_WARN("%s: not create yet", __func__);
+
+}
+#endif
+
+static void btmtk_uart_update_fw_own_timer(struct btmtk_uart_dev *cif_dev)
+{
+
+ if (atomic_read(&cif_dev->fw_own_timer_flag)) {
+ BTMTK_DBG_LIMITTED("update fw own timer");
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_INIT);
+ mod_timer(&cif_dev->fw_own_timer, jiffies + msecs_to_jiffies(FW_OWN_TIMEOUT));
+ } else
+ BTMTK_WARN_LIMITTED("%s: not create yet", __func__);
+}
+
+static void btmtk_uart_create_fw_own_timer(struct btmtk_uart_dev *cif_dev)
+{
+ BTMTK_DBG("%s: create fw own timer", __func__);
+#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE)
+ init_timer(&cif_dev->fw_own_timer);
+ cif_dev->fw_own_timer.function = btmtk_fw_own_timer;
+ cif_dev->fw_own_timer.data = (unsigned long)cif_dev;
+#else
+ timer_setup(&cif_dev->fw_own_timer, btmtk_fw_own_timer, 0);
+#endif
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_INIT);
+}
+
+static void btmtk_uart_delete_fw_own_timer(struct btmtk_uart_dev *cif_dev)
+{
+ if (atomic_read(&cif_dev->fw_own_timer_flag)) {
+ del_timer_sync(&cif_dev->fw_own_timer);
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_UKNOWN);
+ BTMTK_WARN("%s timer deleted", __func__);
+ } else
+ BTMTK_WARN_LIMITTED("%s: not create yet", __func__);
+}
+#endif //(SLEEP_ENABLE == 1)
+
+static int btmtk_uart_open(struct hci_dev *hdev)
+{
+ BTMTK_INFO("%s enter!", __func__);
+ return 0;
+}
+
+static int btmtk_uart_close(struct hci_dev *hdev)
+{
+
+
+ struct btmtk_uart_dev *cif_dev = NULL;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ int ret;
+
+ BTMTK_INFO("%s enter!", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s, bdev is NULL", __func__);
+ return -EINVAL;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ ret = 0;
+
+#if (SLEEP_ENABLE == 1)
+ btmtk_uart_delete_fw_own_timer(cif_dev);
+#endif
+
+#if (USE_DEVICE_NODE == 1)
+ cancel_work_sync(&bdev->reset_waker);
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ /* Clr TX,RX request, let uarthub can sleep */
+ ret = mtk8250_uart_hub_clear_request();
+ if (ret) {
+ BTMTK_ERR("%s mtk8250_uart_hub_clear_request fail ret[%d]", __func__, ret);
+ }
+#endif
+
+ btmtk_tx_thread_exit(bdev->cif_dev);
+
+ btmtk_set_gpio_default();
+ if (connv3_pwr_off(CONNV3_DRV_TYPE_BT))
+ BTMTK_ERR("%s: ConnInfra power off failed!", __func__);
+ btmtk_pwrctrl_post_off();
+#endif
+ BTMTK_INFO("%s end!", __func__);
+
+ return 0;
+}
+
+static int btmtk_send_to_tx_queue(struct btmtk_uart_dev *cif_dev, struct sk_buff *skb)
+{
+ ulong flags = 0;
+
+ /* error handle */
+ if (!atomic_read(&cif_dev->thread_status)) {
+ BTMTK_WARN("%s tx thread already stopped, don't send cmd anymore!!", __func__);
+ /* Removed kfree_skb: leave free to btmtk_main_send_cmd */
+ return -1;
+ }
+
+ spin_lock_irqsave(&cif_dev->tx_lock, flags);
+ skb_queue_tail(&cif_dev->tx_queue, skb);
+ spin_unlock_irqrestore(&cif_dev->tx_lock, flags);
+ wake_up_interruptible(&tx_wait_q);
+
+ return 0;
+}
+
+int btmtk_uart_send_cmd(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type)
+{
+ struct btmtk_uart_dev *cif_dev = NULL;
+ int ret = -1;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("bdev is NULL");
+ ret = -1;
+ /* Removed: leave free to btmtk_main_send_cmd */
+#if 0
+ kfree_skb(skb);
+ skb = NULL;
+#endif
+ goto exit;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("cif_dev is NULL, bdev=%p", bdev);
+ ret = -1;
+ /* Removed: leave free to btmtk_main_send_cmd */
+#if 0
+ kfree_skb(skb);
+ skb = NULL;
+#endif
+ goto exit;
+ }
+
+ /* send pkt through tx_thread or not */
+ /* if want to send_and_recv cmd in tx_thread would not be able to send the pkt */
+ if (pkt_type == BTMTK_TX_PKT_SEND_DIRECT) {
+ BTMTK_WARN("%s send pkt not through tx_thread", __func__);
+ ret = btmtk_cif_send_cmd(bdev, skb->data, skb->len, delay, retry);
+
+ /* in normal case, cif_send success would kfree_skb in tx_thread */
+ /* but in this case, would not pass by tx_thread, so need not kfree_skb */
+ if (ret >= 0) {
+ kfree_skb(skb);
+ skb = NULL;
+ }
+ } else
+ ret = btmtk_send_to_tx_queue(cif_dev, skb);
+
+exit:
+ return ret;
+
+}
+
+static int btmtk_uart_read_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ int ret = 0;
+#if (USE_DEVICE_NODE == 0)
+ u8 cmd[READ_REGISTER_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x0C,
+ 0x01, 0x08, 0x08, 0x00,
+ 0x02, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00};
+
+ u8 event[READ_REGISTER_EVT_HDR_LEN] = {0x04, 0xE4, 0x10, 0x02,
+ 0x08, 0x0C, 0x00, 0x00,
+ 0x00, 0x00, 0x01};
+
+ BTMTK_INFO("%s: read cr %x", __func__, reg);
+
+ memcpy(&cmd[MCU_ADDRESS_OFFSET_CMD], ®, sizeof(reg));
+
+ ret = btmtk_main_send_cmd(bdev, cmd, READ_REGISTER_CMD_LEN, event, READ_REGISTER_EVT_HDR_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ memcpy(val, bdev->io_buf + MCU_ADDRESS_OFFSET_EVT - HCI_TYPE_SIZE, sizeof(u32));
+ *val = le32_to_cpu(*val);
+
+ BTMTK_INFO("%s: reg=%x, value=0x%08x", __func__, reg, *val);
+#endif
+ return ret;
+}
+
+#if (USE_DEVICE_NODE == 0)
+static int btmtk_uart_write_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ int ret = 0;
+
+ u8 cmd[WRITE_REGISTER_CMD_LEN] = { 0x01, 0x6F, 0xFC, 0x14,
+ 0x01, 0x08, 0x10, 0x00,
+ 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF };
+ u8 event[WRITE_REGISTER_EVT_HDR_LEN] = { 0x04, 0xE4, 0x08,
+ 0x02, 0x08, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x01 };
+
+ BTMTK_INFO("%s: write reg=%x, value=0x%08x", __func__, reg, *val);
+ memcpy(&cmd[MCU_ADDRESS_OFFSET_CMD], ®, BT_REG_LEN);
+ memcpy(&cmd[MCU_VALUE_OFFSET_CMD], val, BT_REG_VALUE_LEN);
+
+ ret = btmtk_main_send_cmd(bdev, cmd, WRITE_REGISTER_CMD_LEN, event, WRITE_REGISTER_EVT_HDR_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ return ret;
+}
+#endif
+
+int btmtk_uart_event_filter(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ const u8 read_address_event[READ_ADDRESS_EVT_HDR_LEN] = { 0x4, 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00 };
+ const u8 get_baudrate_event[GETBAUD_EVT_LEN] = { 0x04, 0xE4, 0x0A, 0x02, 0x04, 0x06, 0x00, 0x00, 0x02 };
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE &&
+ skb->len >= event_need_compare_len) {
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+ if ((skb->len == (GETBAUD_EVT_LEN - HCI_TYPE_SIZE + BAUD_SIZE)) &&
+ memcmp(skb->data, &get_baudrate_event[1], GETBAUD_EVT_LEN - 1) == 0) {
+ BTMTK_INFO("%s: GET BAUD = %02X %02X %02X, FC = %02X", __func__,
+ skb->data[10], skb->data[9], skb->data[8], skb->data[11]);
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ } else if ((skb->len == (READ_ADDRESS_EVT_HDR_LEN - HCI_TYPE_SIZE + BD_ADDRESS_SIZE)) &&
+ memcmp(skb->data, &read_address_event[1], READ_ADDRESS_EVT_HDR_LEN - 1) == 0) {
+ memcpy(bdev->bdaddr, &skb->data[READ_ADDRESS_EVT_PAYLOAD_OFFSET - 1], BD_ADDRESS_SIZE);
+ BTMTK_INFO("%s: GET BDADDR = "MACSTR, __func__, MAC2STR(bdev->bdaddr));
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ } else if (memcmp(skb->data, event_need_compare,
+ event_need_compare_len) == 0) {
+ /* if it is wobx debug event, just print in kernel log, drop it
+ * by driver, don't send to stack
+ */
+ if (skb->data[0] == WOBLE_DEBUG_EVT_TYPE)
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: wobx debug log:", __func__);
+
+ /* If driver need to check result from skb, it can get from io_buf */
+ /* Such as chip_id, fw_version, etc. */
+ bdev->io_buf[0] = bt_cb(skb)->pkt_type;
+ memmove(&bdev->io_buf[1], skb->data, skb->len);
+ /* if io_buf is not update timely, it will write wrong number to register
+ * it might make uart pinmux been changed, add delay or print log can avoid this
+ * or mstar member said we can also use dsb(ISHST);
+ */
+#if (USE_DEVICE_NODE == 0)
+ msleep(IO_BUF_DELAY_TIME);
+#endif
+ bdev->recv_evt_len = skb->len;
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ BTMTK_DBG("%s, compare success", __func__);
+ } else {
+ BTMTK_INFO("%s compare fail", __func__);
+ BTMTK_INFO_RAW(event_need_compare, event_need_compare_len,
+ "%s: event_need_compare_len[%d]", __func__, event_need_compare_len);
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: skb->data:", __func__);
+ return 0;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+int btmtk_uart_send_and_recv(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type)
+{
+ unsigned long comp_event_timo = 0, start_time = 0;
+ int ret = 0;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+#if (SLEEP_ENABLE == 1)
+ /* error handle of deadlock */
+ /* if in fw_own, want to send_and_rev cmd */
+ /* cmd would get evt_comp_sem and then wait event */
+ /* however btmtk_uart_driver_own would be triggered before wmt cmd sended */
+ /* then driver_own cmd would not get evt_comp_sem */
+
+ /* can not wait BTMTK_FW_OWNING because would wait for itself */
+ if (cif_dev->own_state == BTMTK_FW_OWN) {
+ BTMTK_INFO("%s: wait driver own", __func__);
+ reinit_completion(&bdev->drv_own_comp);
+ atomic_set(&cif_dev->need_drv_own, 1);
+ wake_up_interruptible(&tx_wait_q);
+ if (!wait_for_completion_timeout(&bdev->drv_own_comp, msecs_to_jiffies(WAIT_DRV_OWN_TIMEOUT)))
+ BTMTK_WARN("%s: wait driver own timeout", __func__);
+ }
+
+ if (cif_dev->own_state == BTMTK_FW_OWN || cif_dev->own_state == BTMTK_OWN_FAIL) {
+ BTMTK_ERR("%s: wait driver own fail", __func__);
+ return -1;
+ }
+
+#endif
+
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s: len[%d] ", __func__, skb->len);
+
+ if (event) {
+ if (event_len > EVENT_COMPARE_SIZE) {
+ BTMTK_ERR("%s, event_len (%d) > EVENT_COMPARE_SIZE(%d), error",
+ __func__, event_len, EVENT_COMPARE_SIZE);
+ return -1;
+ }
+
+ down(&cif_dev->evt_comp_sem);
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE;
+ memcpy(event_need_compare, event + 1, event_len - 1);
+ event_need_compare_len = event_len - 1;
+
+ start_time = jiffies;
+ /* check hci event /wmt event for uart/UART interface, check hci
+ * event for USB interface
+ */
+#if (USE_DEVICE_NODE == 0)
+ comp_event_timo = jiffies + msecs_to_jiffies(WOBLE_COMP_EVENT_TIMO);
+#else
+ comp_event_timo = jiffies + msecs_to_jiffies(2000);
+#endif
+ BTMTK_DBG("event_need_compare_len %d, event_compare_status %d",
+ event_need_compare_len, event_compare_status);
+ } else {
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ }
+
+ ret = btmtk_uart_send_cmd(bdev, skb, delay, retry, pkt_type);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_uart_send_cmd failed!!", __func__);
+ goto fw_assert;
+ }
+
+ do {
+ ret = -1;
+
+ /* check if event_compare_success */
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS) {
+ ret = 0;
+ break;
+ }
+
+ /* error handle*/
+ if (btmtk_get_chip_state(bdev) == BTMTK_STATE_FW_DUMP || !atomic_read(&cif_dev->thread_status)) {
+ BTMTK_WARN("%s thread stopped or fw dumping, don't wait evt anymore!!", __func__);
+ ret = 0;
+ break;
+ }
+
+ usleep_range(10, 100);
+ } while (time_before(jiffies, comp_event_timo));
+
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NOTHING_NEED_COMPARE;
+
+ if (ret == -1) {
+ BTMTK_ERR("%s wait event timeout!!", __func__);
+ bdev->recv_evt_len = 0;
+ ret = -ERRNUM;
+ goto fw_assert;
+ }
+
+ if (event)
+ up(&cif_dev->evt_comp_sem);
+
+ return ret;
+
+
+fw_assert:
+ if (event)
+ up(&cif_dev->evt_comp_sem);
+ //btmtk_send_assert_cmd(bdev);
+ return ret;
+}
+
+int btmtk_uart_send_set_uart_cmd(struct hci_dev *hdev, struct UART_CONFIG *uart_cfg)
+{
+ u8 baud_115200[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x01, 0xC2, 0x01, 0x00, 0x03 };
+ u8 baud_921600[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x00, 0x10, 0x0E, 0x00, 0x03 };
+ u8 baud_3M[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0xC0, 0xC6, 0x2D, 0x00, 0x03 };
+ u8 baud_4M[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x00, 0x09, 0x3D, 0x00, 0x03 };
+ u8 baud_8M[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x00, 0x12, 0x7A, 0x00, 0x03 };
+ u8 baud_10M[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x80, 0x96, 0x98, 0x00, 0x03 };
+ u8 baud_12M[] = { 0x01, 0x6F, 0xFC, 0x0A, 0x01, 0x04,
+ 0x06, 0x00, 0x01, 0x00, 0x1B, 0xB7, 0x00, 0x03 };
+ u8 event[] = {0x04, 0xE4, 0x06, 0x02, 0x04, 0x02, 0x00, 0x00, 0x01};
+ u8 *cmd = NULL;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ int ret = -1;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s, bdev is NULL", __func__);
+ return -EINVAL;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ switch (uart_cfg->iBaudrate) {
+ case 921600:
+ cmd = baud_921600;
+ break;
+ case 3000000:
+ cmd = baud_3M;
+ break;
+ case 4000000:
+ cmd = baud_4M;
+ break;
+ case 8000000:
+ cmd = baud_8M;
+ break;
+ case 10000000:
+ cmd = baud_10M;
+ break;
+ case 12000000:
+ cmd = baud_12M;
+ break;
+ default:
+ /* default chip baud is 115200 */
+ cmd = baud_115200;
+ return 0;
+ //break;
+ }
+
+ switch (uart_cfg->fc) {
+ case UART_HW_FC:
+ cmd[BT_FLOWCTRL_OFFSET] = BT_HW_FC;
+ break;
+ case UART_MTK_SW_FC:
+ case UART_LINUX_FC:
+ cmd[BT_FLOWCTRL_OFFSET] = BT_SW_FC;
+ break;
+ default:
+ /* default disable flow control */
+ cmd[BT_FLOWCTRL_OFFSET] = BT_NONE_FC;
+ }
+
+ /* uarthub setting
+ * ex: 0x13 means hub enable, rhw disable, crc disable
+ */
+ cmd[13] = (cif_dev->fw_hub_en << 4 | !cif_dev->rhw_en << 1 | !cif_dev->crc_en << 0);
+
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, SETBAUD_CMD_LEN, event, SETBAUD_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ return ret;
+ }
+
+ cif_dev->uart_baudrate_set = 1;
+ BTMTK_INFO("%s done", __func__);
+
+ return 0;
+}
+
+static int btmtk_uart_send_query_uart_cmd(struct hci_dev *hdev)
+{
+ u8 cmd[] = { 0x01, 0x6F, 0xFC, 0x05, 0x01, 0x04, 0x01, 0x00, 0x02};
+ u8 event[] = { 0x04, 0xE4, 0x0a, 0x02, 0x04, 0x06, 0x00, 0x00, 0x02};
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ int ret = -1;
+
+ ret = btmtk_main_send_cmd(bdev, cmd, GETBAUD_CMD_LEN, event, GETBAUD_EVT_LEN, DELAY_TIMES,
+ RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_uart_send_query_uart_cmd failed!!", __func__);
+ return ret;
+ }
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+int btmtk_uart_send_wakeup_cmd(struct hci_dev *hdev)
+{
+ u8 cmd[] = { 0x01, 0x6f, 0xfc, 0x01, 0xFF };
+ /* event before fw dl */
+ u8 event[] = { 0x04, 0xE4, 0x06, 0x02, 0x03, 0x02, 0x00, 0x00, 0x03};
+ /* event after fw dl */
+ u8 event2[] = { 0x04, 0xE4, 0x07, 0x02, 0x03, 0x03, 0x00, 0x00, 0x03, 0x01 };
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_uart_dev *cif_dev = NULL;
+ int ret = -1;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ if (cif_dev->uart_baudrate_set == 0) {
+ BTMTK_INFO("%s uart baudrate is 115200, no need", __func__);
+ return 0;
+ }
+ if (is_mt6639(bdev->chip_id) || is_mt66xx(bdev->chip_id)) {
+ if (cif_dev->fw_dl_ready)
+ ret = btmtk_main_send_cmd(bdev, cmd+4, 1, event2, WAKEUP_EVT_LEN + 1,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ else
+ ret = btmtk_main_send_cmd(bdev, cmd+4, 1, event, WAKEUP_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+ } else
+ ret = btmtk_main_send_cmd(bdev, cmd, WAKEUP_CMD_LEN, event, WAKEUP_EVT_LEN,
+ 0, 0, BTMTK_TX_CMD_FROM_DRV);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ return ret;
+ }
+
+ BTMTK_INFO("%s done", __func__);
+ return ret;
+}
+
+#if (USE_DEVICE_NODE == 0)
+static int btmtk_uart_subsys_reset(struct btmtk_dev *bdev)
+{
+ struct ktermios new_termios;
+ struct tty_struct *tty;
+ struct UART_CONFIG uart_cfg;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ int ret = -1;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ uart_cfg = cif_dev->uart_cfg;
+ tty = cif_dev->tty;
+ new_termios = tty->termios;
+
+ BTMTK_INFO("%s tigger reset pin: %d", __func__, bdev->bt_cfg.dongle_reset_gpio_pin);
+ gpio_set_value(bdev->bt_cfg.dongle_reset_gpio_pin, 0);
+ msleep(SUBSYS_RESET_GPIO_DELAY_TIME);
+ gpio_set_value(bdev->bt_cfg.dongle_reset_gpio_pin, 1);
+ /* Basically, we need to polling the cr (BT_MISC) untill the subsys reset is completed
+ * However, there is no uart_hw mechnism in buzzard, we can't read the info from controller now
+ * use msleep instead currently
+ */
+ msleep(SUBSYS_RESET_GPIO_DELAY_TIME);
+
+ /* Flush any pending characters in the driver and discipline. */
+ tty_ldisc_flush(tty);
+ tty_driver_flush_buffer(tty);
+
+ /* set tty host baud and flowcontrol to default value */
+ BTMTK_INFO("Set default baud: %d, disable flowcontrol", BT_UART_DEFAULT_BAUD);
+ tty_termios_encode_baud_rate(&new_termios, BT_UART_DEFAULT_BAUD, BT_UART_DEFAULT_BAUD);
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH|CRTSCTS);
+ tty_set_termios(tty, &new_termios);
+
+ /* set chip baud and flowcontrol to config setting */
+ ret = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_uart_send_set_uart_cmd failed!!", __func__);
+ goto exit;
+ }
+
+ /* set tty host baud and flowcontrol to config setting */
+ BTMTK_INFO("Set config baud: %d, flowcontrol: %d", uart_cfg.iBaudrate, uart_cfg.fc);
+ tty_termios_encode_baud_rate(&new_termios, uart_cfg.iBaudrate, uart_cfg.iBaudrate);
+
+ switch (uart_cfg.fc) {
+ /* HW FC Enable */
+ case UART_HW_FC:
+ new_termios.c_cflag |= CRTSCTS;
+ new_termios.c_iflag &= ~(NOFLSH);
+ break;
+ /* Linux Software FC */
+ case UART_LINUX_FC:
+ new_termios.c_iflag |= (IXON | IXOFF | IXANY);
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH);
+ break;
+ /* MTK Software FC */
+ case UART_MTK_SW_FC:
+ new_termios.c_iflag |= CRTSCTS;
+ new_termios.c_cflag &= ~(NOFLSH);
+ break;
+ /* default disable flow control */
+ default:
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH|CRTSCTS);
+ }
+
+ tty_set_termios(tty, &new_termios);
+ ret = btmtk_uart_send_wakeup_cmd(bdev->hdev);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_uart_send_set_uart_cmd failed!!", __func__);
+ goto exit;
+ }
+
+ BTMTK_INFO("%s done", __func__);
+
+exit:
+ return ret;
+}
+
+
+#else // (USE_DEVICE_NODE == 1)
+static int btmtk_uart_subsys_reset(struct btmtk_dev *bdev)
+{
+ BTMTK_DBG("%s sp no need to reset flow, bt on/off directly", __func__);
+ return 0;
+}
+
+static int btmtk_sp_pre_open(struct btmtk_dev *bdev)
+{
+ struct ktermios new_termios;
+ struct tty_struct *tty;
+ struct UART_CONFIG uart_cfg;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ int ret = -1;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ uart_cfg = cif_dev->uart_cfg;
+ tty = cif_dev->tty;
+ new_termios = tty->termios;
+
+ btmtk_pwrctrl_pre_on(bdev);
+ if (connv3_pwr_on(CONNV3_DRV_TYPE_BT)) {
+ BTMTK_ERR("ConnInfra power on failed!");
+ return -EFAULT;
+ }
+
+ /* start tx_thread */
+ if (btmtk_tx_thread_start(bdev))
+ return -EFAULT;
+
+ btmtk_set_uart_auxFunc();
+
+ /* temp solution wait pmic enable */
+ msleep(100);
+
+ if (connv3_ext_32k_on()) {
+ BTMTK_ERR("connv3_ext_32k_on failed!");
+ return -EFAULT;
+ }
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ if (cif_dev->hub_en) {
+ /* use uarthub multi-host mode (default) */
+ ret = mtk8250_uart_hub_enable_bypass_mode(0);
+ BTMTK_INFO("%s mtk8250_uart_hub_enable_bypass_mode(0) ret[%d]", __func__, ret);
+
+ ret = btmtk_wakeup_uarthub();
+
+ /* use uarthub bypass mode */
+ ret = mtk8250_uart_hub_enable_bypass_mode(1);
+ BTMTK_INFO("%s mtk8250_uart_hub_enable_bypass_mode(1) ret[%d]", __func__, ret);
+ }
+#endif
+ /* set tty host baud and flowcontrol to default value */
+ BTMTK_INFO("Set default baud: %d, disable flowcontrol", BT_UART_DEFAULT_BAUD);
+ tty_termios_encode_baud_rate(&new_termios, BT_UART_DEFAULT_BAUD, BT_UART_DEFAULT_BAUD);
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH|CRTSCTS);
+ tty_set_termios(tty, &new_termios);
+
+ /* update baurdrate from dts */
+ if (cif_dev->baudrate)
+ uart_cfg.iBaudrate = cif_dev->baudrate;
+
+ /* uarhub setting */
+ cif_dev->fw_hub_en = 0;
+ cif_dev->rhw_en = 0;
+ cif_dev->crc_en = 0;
+ cif_dev->fw_dl_ready = 0;
+ cif_dev->flush_en = 1;
+
+ /* set chip baud and flowcontrol to config setting */
+ ret = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+ if (ret < 0) {
+ BTMTK_WARN("%s retry send cmd", __func__);
+ ret = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+ if (ret < 0)
+ goto exit;
+ }
+
+ /* set tty host baud and flowcontrol to config setting */
+ BTMTK_INFO("Set config baud: %d, flowcontrol: %d", uart_cfg.iBaudrate, uart_cfg.fc);
+
+ tty_termios_encode_baud_rate(&new_termios, uart_cfg.iBaudrate, uart_cfg.iBaudrate);
+
+ switch (uart_cfg.fc) {
+ /* HW FC Enable */
+ case UART_HW_FC:
+ new_termios.c_cflag |= CRTSCTS;
+ new_termios.c_iflag &= ~(NOFLSH);
+ break;
+ /* Linux Software FC */
+ case UART_LINUX_FC:
+ new_termios.c_iflag |= (IXON | IXOFF | IXANY);
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH);
+ break;
+ /* MTK Software FC */
+ case UART_MTK_SW_FC:
+ new_termios.c_iflag |= CRTSCTS;
+ new_termios.c_cflag &= ~(NOFLSH);
+ break;
+ /* default disable flow control */
+ default:
+ new_termios.c_cflag &= ~(CRTSCTS);
+ new_termios.c_iflag &= ~(NOFLSH|CRTSCTS);
+ }
+
+ tty_set_termios(tty, &new_termios);
+
+ ret = btmtk_uart_send_wakeup_cmd(bdev->hdev);
+ if (ret < 0)
+ goto exit;
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ if (cif_dev->hub_en) {
+ /* disable ADSP,MD when fw dl */
+ ret = mtk8250_uart_hub_fifo_ctrl(1);
+ BTMTK_INFO("%s: Set mtk8250_uart_hub_fifo_ctrl(1) ret[%d]", __func__, ret);
+ }
+#endif
+
+
+ ret = btmtk_load_rom_patch(bdev);
+ cif_event = HIF_EVENT_PROBE;
+ cif_state = &bdev->cif_state[cif_event];
+ /* Set End/Error state */
+ if (ret == 0) {
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ } else {
+ BTMTK_ERR("%s: btmtk_load_rom_patch failed (%d)", __func__, ret);
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ if (cif_dev->hub_en) {
+ /* enable ADSP,MD when fw dl done*/
+ ret = mtk8250_uart_hub_fifo_ctrl(0);
+ BTMTK_INFO("%s: Set mtk8250_uart_hub_fifo_ctrl(0) ret[%d]", __func__, ret);
+
+ /* uarhub setting */
+ cif_dev->fw_hub_en = 1;
+ cif_dev->rhw_en = 1;
+ cif_dev->crc_en = 1;
+ cif_dev->fw_dl_ready = 1;
+
+ /* set chip baud and flowcontrol to config setting */
+ ret = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+ if (ret < 0) {
+ BTMTK_WARN("%s after fwdl, send uarhub setting cmd", __func__);
+ goto exit;
+ }
+
+ /* after fw dl, use uarthub multi-host mode */
+ ret = mtk8250_uart_hub_enable_bypass_mode(0);
+ BTMTK_INFO("%s after fw dl, mtk8250_uart_hub_enable_bypass_mode(0) ret[%d]", __func__, ret);
+
+ ret = btmtk_uart_send_wakeup_cmd(bdev->hdev);
+ if (ret < 0)
+ goto exit;
+ }
+#endif
+
+ BTMTK_INFO("%s done", __func__);
+
+exit:
+ return ret;
+}
+
+#endif //(USE_DEVICE_NODE)
+
+static int btmtk_uart_pre_open(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+#if (SLEEP_ENABLE == 1)
+ struct btmtk_uart_dev *cif_dev = NULL;
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ BTMTK_INFO("%s init to driver own state", __func__);
+ /* not start fw_own_timer until bt open done */
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_UKNOWN);
+ cif_dev->own_state = BTMTK_DRV_OWN;
+#endif
+
+#if (USE_DEVICE_NODE == 1)
+ ret = btmtk_sp_pre_open(bdev);
+#endif
+
+ return ret;
+}
+
+static void btmtk_uart_open_done(struct btmtk_dev *bdev)
+{
+#if (SLEEP_ENABLE == 1)
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ BTMTK_INFO("%s start", __func__);
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ /* start fw own timer */
+ btmtk_uart_create_fw_own_timer(cif_dev);
+#endif
+
+#if (USE_DEVICE_NODE == 1)
+ if (connv3_pwr_on_done(CONNV3_DRV_TYPE_BT))
+ BTMTK_ERR("%s: ConnInfra power done failed!", __func__);
+#endif
+
+}
+
+
+static void btmtk_uart_waker_notify(struct btmtk_dev *bdev)
+{
+ BTMTK_INFO("%s enter!", __func__);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+ schedule_work(&bdev->reset_waker);
+}
+
+static int btmtk_uart_set_para(struct btmtk_dev *bdev, int val)
+{
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ BTMTK_INFO("%s start val[%d]", __func__, val);
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ cif_dev->hub_en = !!(val & BTMTK_HUB_EN);
+ cif_dev->sleep_en = !!(val & BTMTK_SLEEP_EN);
+
+ BTMTK_INFO("%s hub_en[%d] sleep_en[%d]", __func__, cif_dev->hub_en, cif_dev->sleep_en);
+ return 0;
+}
+
+
+static void btmtk_uart_cif_mutex_lock(struct btmtk_dev *bdev)
+{
+ UART_OPS_MUTEX_LOCK();
+}
+
+static void btmtk_uart_cif_mutex_unlock(struct btmtk_dev *bdev)
+{
+ UART_OPS_MUTEX_UNLOCK();
+}
+
+static void btmtk_uart_chip_reset_notify(struct btmtk_dev *bdev)
+{
+ //struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+}
+
+static int btmtk_uart_load_fw_patch_using_dma(struct btmtk_dev *bdev, u8 *image,
+ u8 *fwbuf, int section_dl_size, int section_offset)
+{
+ int cur_len = 0;
+ int count = 0;
+ int ret = -1;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ s32 sent_len;
+ u8 cmd[LD_PATCH_CMD_LEN] = {0x02, 0x6F, 0xFC, 0x05, 0x00, 0x01, 0x01, 0x01, 0x00, PATCH_PHASE3};
+ u8 event[LD_PATCH_EVT_LEN] = {0x04, 0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[7] is status*/
+
+ if (bdev == NULL || image == NULL || fwbuf == NULL) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ ret = -1;
+ goto exit;
+ }
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ BTMTK_INFO("%s: loading rom patch... start", __func__);
+ while (1) {
+ sent_len = (section_dl_size - cur_len) >= (UPLOAD_PATCH_UNIT) ?
+ (UPLOAD_PATCH_UNIT) : (section_dl_size - cur_len);
+ /* wait tty buffer clean */
+ if (cif_dev->flush_en) {
+ do {
+ count = tty_chars_in_buffer(cif_dev->tty);
+ //BTMTK_DBG("%s: char in buffer before flush count[%d]", __func__, count);
+ } while (count != 0);
+ tty_driver_flush_buffer(cif_dev->tty);
+ }
+
+ if (sent_len > 0) {
+ memcpy(image, fwbuf + section_offset + cur_len, sent_len);
+
+ ret = cif_dev->tty->ops->write(cif_dev->tty, image, sent_len);
+
+ // can use next cur_len - current cur_len = ret
+ //BTMTK_DBG("%s, send length: ret= %d", __func__, ret);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s: send patch failed, terminate", __func__);
+ goto exit;
+ }
+ cur_len += ret;
+ } else
+ break;
+ }
+
+ BTMTK_INFO("%s: send dl cmd", __func__);
+ ret = btmtk_main_send_cmd(bdev,
+ cmd, LD_PATCH_CMD_LEN,
+ event, LD_PATCH_EVT_LEN,
+ PATCH_DOWNLOAD_PHASE3_DELAY_TIME,
+ PATCH_DOWNLOAD_PHASE3_RETRY,
+ BTMTK_TX_ACL_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: send wmd dl cmd failed, terminate!", __func__);
+ return ret;
+ }
+ BTMTK_INFO("%s: loading rom patch... Done", __func__);
+
+exit:
+ return ret;
+}
+
+int btmtk_cif_send_cmd(struct btmtk_dev *bdev, const uint8_t *cmd,
+ const int cmd_len, int retry, int delay)
+{
+ int ret = -1, len = 0, count = 0;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ u8 assert_cmd[ASSERT_CMD_LEN] = { 0x01, 0x5B, 0xFD, 0x00 };
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ /* BTMTK_INFO("%s: tty %p\n", __func__, bdev->tty); */
+
+ /* wait tty buffer clean */
+ if (cif_dev->flush_en) {
+ do {
+ count = tty_chars_in_buffer(cif_dev->tty);
+ //BTMTK_DBG("%s: char in buffer before flush count[%d]", __func__, count);
+ } while (count != 0);
+
+ tty_driver_flush_buffer(cif_dev->tty);
+ }
+
+ count = 0;
+
+ if(cmd_len == ASSERT_CMD_LEN && memcmp(assert_cmd, cmd, ASSERT_CMD_LEN) == 0) {
+ BTMTK_INFO("%s: trigger assert", __func__);
+ btmtk_set_chip_state(bdev, BTMTK_STATE_SEND_ASSERT);
+ }
+
+ while (len != cmd_len && count < BTMTK_MAX_SEND_RETRY) {
+ ret = cif_dev->tty->ops->write(cif_dev->tty, cmd + len, cmd_len - len);
+ len += ret;
+ count++;
+ }
+
+ if (count == BTMTK_MAX_SEND_RETRY) {
+ BTMTK_ERR("%s: retry[%d] fail", __func__, count);
+ ret = -1;
+ }
+
+ BTMTK_INFO_RAW(cmd, cmd_len, "%s, len[%d] retry[%d] room[%d] CMD : ", __func__, cmd_len,
+ count, tty_write_room(cif_dev->tty));
+
+ return ret;
+}
+
+/* bt_tx_wait_for_msg
+ *
+ * Check needing action of current bt status to wake up bt thread
+ *
+ * Arguments:
+ * [IN] bdev - bt driver control strcuture
+ *
+ * Return Value:
+ * return check - 1 for waking up bt thread, 0 otherwise
+ *
+ */
+static u32 btmtk_thread_wait_for_msg(struct btmtk_dev *bdev)
+{
+ u32 ret = 0;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ if (!skb_queue_empty(&cif_dev->tx_queue)) {
+ ret |= BTMTK_THREAD_TX;
+ }
+
+#if (SLEEP_ENABLE == 1)
+ if (atomic_read(&cif_dev->need_drv_own)) {
+ //BTMTK_DBG("%s: set drv own", __func__);
+ atomic_set(&cif_dev->need_drv_own, 0);
+ ret |= BTMTK_THREAD_RX;
+ }
+
+ if (atomic_read(&cif_dev->fw_own_timer_flag) == FW_OWN_TIMER_RUNNING) {
+ //BTMTK_DBG("%s: set fw own", __func__);
+ /* incase of tx_thread keep running for FW_OWN_TIMER_RUNNING */
+ atomic_set(&cif_dev->fw_own_timer_flag, FW_OWN_TIMER_DONE);
+ ret |= BTMTK_THREAD_FW_OWN;
+ }
+#endif
+
+ if (kthread_should_stop()) {
+ BTMTK_DBG("%s: kthread_should_stop", __func__);
+ ret |= BTMTK_THREAD_STOP;
+ }
+
+ return ret;
+}
+
+static int btmtk_uart_tx_thread(void *data)
+{
+ struct btmtk_dev *bdev = data;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ int state = BTMTK_STATE_INIT;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct sk_buff *skb;
+ u32 thread_flag = 0;
+ int ret = 0;
+ ulong flags = 0;
+
+ BTMTK_INFO("%s start", __func__);
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+ /* avoid unused var for USE_DEVICE_NODE == 0*/
+ ret = 0;
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ atomic_set(&cif_dev->thread_status, 1);
+
+ while (1) {
+ wait_event_interruptible(tx_wait_q,
+ (thread_flag = btmtk_thread_wait_for_msg(bdev)));
+
+ if (thread_flag & BTMTK_THREAD_STOP) {
+ BTMTK_WARN("%s: thread is stopped, break", __func__);
+ break;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ fstate = btmtk_fops_get_state(bdev);
+
+#if (SLEEP_ENABLE == 1)
+ if (fstate == BTMTK_FOPS_STATE_CLOSING) {
+ //BTMTK_DBG("%s: no fw own when closing", __func__);
+ thread_flag &= ~BTMTK_THREAD_FW_OWN;
+ }
+
+ if (state == BTMTK_STATE_FW_DUMP || state == BTMTK_STATE_SEND_ASSERT
+ || state == BTMTK_STATE_SUBSYS_RESET) {
+ //BTMTK_DBG("%s: no fw/driver own, no tx when dumping", __func__);
+ /* if disable tx would not send rhw debug sop */
+ thread_flag &= ~(BTMTK_THREAD_FW_OWN);
+ /* incase of aftrer fw coredump, send fw own fail and trigger assert again */
+ btmtk_uart_delete_fw_own_timer(cif_dev);
+ }
+
+ if (thread_flag & (BTMTK_THREAD_TX | BTMTK_THREAD_RX)) {
+ ret = btmtk_uart_driver_own(bdev);
+ if (ret) {
+ BTMTK_ERR("%s: set driver own return fail", __func__);
+ //btmtk_reset_trigger(bdev);
+ btmtk_send_assert_cmd(bdev);
+ break;
+ }
+
+ /* if bt fw closed, no need to send fw own */
+ } else if (thread_flag & BTMTK_THREAD_FW_OWN) {
+ ret = btmtk_uart_fw_own(bdev);
+ if (ret) {
+ BTMTK_ERR("%s: set fw own return fail", __func__);
+ //btmtk_reset_trigger(bdev);
+ btmtk_send_assert_cmd(bdev);
+ break;
+ }
+ }
+#endif
+
+ if (thread_flag & BTMTK_THREAD_TX) {
+ if (cif_dev->own_state != BTMTK_DRV_OWN) {
+ BTMTK_WARN("%s not in dirver_own state[%d] can not send cmd", __func__, cif_dev->own_state);
+ continue;
+ }
+ while (skb_queue_len(&cif_dev->tx_queue)) {
+ spin_lock_irqsave(&cif_dev->tx_lock, flags);
+ skb = skb_dequeue(&cif_dev->tx_queue);
+ spin_unlock_irqrestore(&cif_dev->tx_lock, flags);
+ if (skb) {
+ btmtk_cif_send_cmd(bdev,
+ skb->data, skb->len,
+ 5, 0);
+ kfree_skb(skb);
+ skb = NULL;
+ }
+ }
+ }
+ }
+ atomic_set(&cif_dev->thread_status, 0);
+ BTMTK_INFO("%s end", __func__);
+ return 0;
+}
+
+static int btmtk_tx_thread_start(struct btmtk_dev *bdev)
+{
+ int i = 0;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s start", __func__);
+
+ if (!atomic_read(&cif_dev->thread_status)) {
+ cif_dev->tx_task = kthread_run(btmtk_uart_tx_thread,
+ bdev, "btmtk_uart_tx_thread");
+ if (IS_ERR(cif_dev->tx_task)) {
+ BTMTK_ERR("%s create tx thread FAILED", __func__);
+ return -1;
+ }
+
+ while (!atomic_read(&cif_dev->thread_status) && i < RETRY_TIMES) {
+ BTMTK_INFO("%s: wait btmtk_uart_tx_thread start ...", __func__);
+ msleep(100);
+ i++;
+ if (i == RETRY_TIMES - 1) {
+ BTMTK_INFO("%s: wait btmtk_uart_tx_thread start failed", __func__);
+ return -1;
+ }
+ }
+
+ BTMTK_INFO("%s started", __func__);
+ } else {
+ BTMTK_INFO("%s already running", __func__);
+ }
+ skb_queue_purge(&cif_dev->tx_queue);
+
+
+ return 0;
+}
+
+
+static int btmtk_tx_thread_exit(struct btmtk_uart_dev *cif_dev)
+{
+ int i = 0;
+ BTMTK_INFO("%s start", __func__);
+
+ if (cif_dev == NULL) {
+ BTMTK_ERR("%s: cif_dev is NULL", __func__);
+ return -1;
+ }
+
+ if (!IS_ERR(cif_dev->tx_task) && atomic_read(&cif_dev->thread_status)) {
+ kthread_stop(cif_dev->tx_task);
+
+ while (atomic_read(&cif_dev->thread_status) && i < RETRY_TIMES) {
+ BTMTK_INFO("%s: wait btmtk_uart_tx_thread stop ...", __func__);
+ msleep(100);
+ i++;
+ if (i == RETRY_TIMES - 1) {
+ BTMTK_INFO("%s: wait btmtk_uart_tx_thread stop failed", __func__);
+ break;
+ }
+ }
+ }
+ skb_queue_purge(&cif_dev->tx_queue);
+
+ BTMTK_INFO("%s done", __func__);
+ return 0;
+}
+
+/* Allocate Uart-Related memory */
+static int btmtk_uart_allocate_memory(void)
+{
+ return 0;
+}
+
+int btmtk_cif_send_calibration(struct btmtk_dev *bdev)
+{
+ return 0;
+}
+
+#if (USE_DEVICE_NODE == 0)
+static int btmtk_uart_set_pinmux(struct btmtk_dev *bdev)
+{
+ int err = 0;
+ u32 val;
+
+ /* BT_PINMUX_CTRL_REG setup */
+ btmtk_uart_read_register(bdev, BT_PINMUX_CTRL_REG, &val);
+ val |= BT_PINMUX_CTRL_ENABLE;
+ err = btmtk_uart_write_register(bdev, BT_PINMUX_CTRL_REG, &val);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_uart_write_register failed!");
+ return -1;
+ }
+ btmtk_uart_read_register(bdev, BT_PINMUX_CTRL_REG, &val);
+
+ /* BT_PINMUX_CTRL_REG setup */
+ btmtk_uart_read_register(bdev, BT_SUBSYS_RST_REG, &val);
+ val |= BT_SUBSYS_RST_ENABLE;
+ err = btmtk_uart_write_register(bdev, BT_SUBSYS_RST_REG, &val);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_uart_write_register failed!");
+ return -1;
+ }
+ btmtk_uart_read_register(bdev, BT_SUBSYS_RST_REG, &val);
+
+ BTMTK_INFO("%s done", __func__);
+ return 0;
+}
+#endif
+
+static int btmtk_uart_init(struct btmtk_dev *bdev)
+{
+ int err = 0;
+
+ err = btmtk_main_cif_initialize(bdev, HCI_UART);
+ if (err < 0) {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed!");
+ goto end;
+ }
+
+#if (USE_DEVICE_NODE == 0)
+ err = btmtk_register_hci_device(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_register_hci_device failed!");
+ goto deinit;
+ }
+
+ err = btmtk_uart_set_pinmux(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_uart_set_pinmux failed!");
+ goto free_hci;
+ }
+#endif
+
+ INIT_WORK(&bdev->reset_waker, btmtk_reset_waker);
+ goto end;
+
+#if (USE_DEVICE_NODE == 0)
+free_hci:
+ btmtk_deregister_hci_device(bdev);
+deinit:
+ btmtk_main_cif_uninitialize(bdev, HCI_UART);
+#endif
+end:
+ BTMTK_INFO("%s done", __func__);
+ return err;
+}
+
+/* ------ LDISC part ------ */
+/* btmtk_uart_tty_probe
+ *
+ * Called when line discipline changed to HCI_UART.
+ *
+ * Arguments:
+ * tty pointer to tty info structure
+ * Return Value:
+ * 0 if success, otherwise error code
+ */
+static int btmtk_uart_tty_probe(struct tty_struct *tty)
+{
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+
+ bdev = dev_get_drvdata(tty->dev);
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ return -ENOMEM;
+ }
+
+ /* Init tty-related operation */
+ tty->receive_room = 65536;
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ tty->port->low_latency = 1;
+#endif
+
+ btmtk_uart_allocate_memory();
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ tty->disc_data = bdev;
+ cif_dev->tty = tty;
+ dev_set_drvdata(tty->dev, bdev);
+
+ spin_lock_init(&cif_dev->tx_lock);
+ skb_queue_head_init(&cif_dev->tx_queue);
+
+ /* start tx_thread */
+ if (btmtk_tx_thread_start(bdev))
+ return -EFAULT;
+
+ cif_dev->stp_cursor = 2;
+ cif_dev->stp_dlen = 0;
+
+ /* definition changed!! */
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+
+ tty_driver_flush_buffer(tty);
+
+ BTMTK_INFO("%s: tty done %p", __func__, tty);
+
+ return 0;
+}
+
+/* btmtk_uart_tty_disconnect
+ *
+ * Called when the line discipline is changed to something
+ * else, the tty is closed, or the tty detects a hangup.
+ */
+static void btmtk_uart_tty_disconnect(struct tty_struct *tty)
+{
+ struct btmtk_dev *bdev = tty->disc_data;
+#if (USE_DEVICE_NODE == 0)
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ btmtk_woble_uninitialize(&cif_dev->bt_woble);
+#endif
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+ cancel_work_sync(&bdev->reset_waker);
+ btmtk_tx_thread_exit(bdev->cif_dev);
+ btmtk_main_cif_disconnect_notify(bdev, HCI_UART);
+}
+
+/*
+ * We don't provide read/write/poll interface for user space.
+ */
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+static ssize_t btmtk_uart_tty_read(struct tty_struct *tty, struct file *file,
+ unsigned char *buf, size_t count)
+#else
+static ssize_t btmtk_uart_tty_read(struct tty_struct *tty, struct file *file,
+ unsigned char *buf, size_t nr,
+ void **cookie, unsigned long offset)
+#endif
+{
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+ return 0;
+}
+
+static ssize_t btmtk_uart_tty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *data, size_t count)
+{
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+ return 0;
+}
+
+static unsigned int btmtk_uart_tty_poll(struct tty_struct *tty, struct file *filp, poll_table *wait)
+{
+ unsigned int mask = 0;
+ struct btmtk_dev *bdev = tty->disc_data;
+ struct btmtk_uart_dev *cif_dev = bdev->cif_dev;
+
+ if (cif_dev->subsys_reset == 1) {
+ mask |= POLLIN | POLLRDNORM; /* readable */
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+ }
+ return mask;
+}
+
+/* btmtk_uart_tty_ioctl()
+ *
+ * Process IOCTL system call for the tty device.
+ *
+ * Arguments:
+ *
+ * tty pointer to tty instance data
+ * file pointer to open file object for device
+ * cmd IOCTL command code
+ * arg argument for IOCTL call (cmd dependent)
+ *
+ * Return Value: Command dependent
+ */
+static int btmtk_uart_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+#if (USE_DEVICE_NODE == 0)
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+#endif
+ struct UART_CONFIG uart_cfg;
+ struct btmtk_dev *bdev = tty->disc_data;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s: tty %p cmd = %u", __func__, tty, cmd);
+
+ switch (cmd) {
+ case HCIUARTSETPROTO:
+ BTMTK_INFO("%s: <!!> Set low_latency to TRUE <!!>", __func__);
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ tty->port->low_latency = 1;
+#endif
+ break;
+ case HCIUARTSETBAUD:
+ if (copy_from_user(&uart_cfg, (struct UART_CONFIG __user *)arg,
+ sizeof(struct UART_CONFIG)))
+ return -ENOMEM;
+ cif_dev->uart_cfg = uart_cfg;
+ BTMTK_INFO("%s: <!!> Set BAUDRATE, fc = %d iBaudrate = %d <!!>",
+ __func__, (int)uart_cfg.fc, uart_cfg.iBaudrate);
+#if (USE_DEVICE_NODE == 0)
+ err = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+#endif
+ break;
+ case HCIUARTSETWAKEUP:
+ BTMTK_INFO("%s: <!!> Send Wakeup <!!>", __func__);
+#if (USE_DEVICE_NODE == 0)
+ err = btmtk_uart_send_wakeup_cmd(bdev->hdev);
+#endif
+ break;
+ case HCIUARTGETBAUD:
+ BTMTK_INFO("%s: <!!> Get BAUDRATE <!!>", __func__);
+#if (USE_DEVICE_NODE == 0)
+ err = btmtk_uart_send_query_uart_cmd(bdev->hdev);
+#endif
+ break;
+ case HCIUARTSETSTP:
+ BTMTK_INFO("%s: <!!> Set STP mandatory command <!!>", __func__);
+ break;
+ case HCIUARTLOADPATCH:
+
+#if (USE_DEVICE_NODE == 0)
+ BTMTK_INFO("%s: <!!> Set HCIUARTLOADPATCH command <!!>", __func__);
+
+ err = btmtk_load_rom_patch(bdev);
+ cif_event = HIF_EVENT_PROBE;
+ cif_state = &bdev->cif_state[cif_event];
+ /* Set End/Error state */
+ if (err == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else {
+ BTMTK_ERR("%s: Set HCIUARTLOADPATCH command failed (%d)", __func__, err);
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ }
+
+ err = btmtk_woble_initialize(bdev, &cif_dev->bt_woble);
+ if (err < 0)
+ BTMTK_ERR("btmtk_woble_initialize failed!");
+ else
+ BTMTK_ERR("btmtk_woble_initialize");
+#endif
+ break;
+ case HCIUARTINIT:
+ BTMTK_INFO("%s: <!!> Set HCIUARTINIT <!!>", __func__);
+ err = btmtk_uart_init(bdev);
+ break;
+ default:
+ /* pr_info("<!!> n_tty_ioctl_helper <!!>\n"); */
+ err = n_tty_ioctl_helper(tty, file, cmd, arg);
+ break;
+ };
+
+ return err;
+}
+
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 4, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+static long btmtk_uart_tty_compat_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+#else
+static int btmtk_uart_tty_compat_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+#endif
+{
+ int err = 0;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct UART_CONFIG uart_cfg;
+ struct btmtk_dev *bdev = tty->disc_data;
+ struct btmtk_uart_dev *cif_dev = NULL;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+
+ BTMTK_INFO("%s: tty %p cmd = %u", __func__, tty, cmd);
+
+ switch (cmd) {
+ case HCIUARTSETPROTO:
+ BTMTK_INFO("%s: <!!> Set low_latency to TRUE <!!>", __func__);
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ tty->port->low_latency = 1;
+#endif
+ break;
+ case HCIUARTSETBAUD:
+ if (copy_from_user(&uart_cfg, (struct UART_CONFIG __user *)arg,
+ sizeof(struct UART_CONFIG)))
+ return -ENOMEM;
+ cif_dev->uart_cfg = uart_cfg;
+ BTMTK_INFO("%s: <!!> Set BAUDRATE, fc = %d iBaudrate = %d <!!>",
+ __func__, (int)uart_cfg.fc, uart_cfg.iBaudrate);
+ err = btmtk_uart_send_set_uart_cmd(bdev->hdev, &uart_cfg);
+ break;
+ case HCIUARTSETWAKEUP:
+ BTMTK_INFO("%s: <!!> Send Wakeup <!!>", __func__);
+ err = btmtk_uart_send_wakeup_cmd(bdev->hdev);
+ break;
+ case HCIUARTGETBAUD:
+ BTMTK_INFO("%s: <!!> Get BAUDRATE <!!>", __func__);
+ err = btmtk_uart_send_query_uart_cmd(bdev->hdev);
+ break;
+ case HCIUARTSETSTP:
+ BTMTK_INFO("%s: <!!> Set STP mandatory command <!!>", __func__);
+ break;
+ case HCIUARTLOADPATCH:
+ BTMTK_INFO("%s: <!!> Set HCIUARTLOADPATCH command <!!>", __func__);
+ err = btmtk_load_rom_patch(bdev);
+ cif_event = HIF_EVENT_PROBE;
+ cif_state = &bdev->cif_state[cif_event];
+ /* Set End/Error state */
+ if (err == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else {
+ BTMTK_ERR("%s: Set HCIUARTLOADPATCH command failed (%d)", __func__, err);
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ }
+
+ err = btmtk_woble_initialize(bdev, &cif_dev->bt_woble);
+ if (err < 0)
+ BTMTK_ERR("btmtk_woble_initialize failed!");
+ else
+ BTMTK_ERR("btmtk_woble_initialize");
+
+ break;
+ case HCIUARTINIT:
+ BTMTK_INFO("%s: <!!> Set HCIUARTINIT <!!>", __func__);
+ err = btmtk_uart_init(bdev);
+ break;
+ default:
+ /* pr_info("<!!> n_tty_ioctl_helper <!!>\n"); */
+ err = n_tty_ioctl_helper(tty, file, cmd, arg);
+ break;
+ };
+
+ return err;
+}
+
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+static void btmtk_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *flags, int count)
+#else
+static void btmtk_uart_tty_receive(struct tty_struct *tty, const u8 *data, const char *flags, int count)
+#endif
+{
+ int ret = -1;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+ struct btmtk_dev *bdev = tty->disc_data;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if (fstate == BTMTK_FOPS_STATE_CLOSED) {
+ BTMTK_DBG_RAW(data, count, "[SKIP] %s: count[%d]", __func__, count);
+ return;
+ }
+
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+#if (SLEEP_ENABLE == 1)
+ //BTMTK_INFO_RAW(data, count, "%s: count[%d]", __func__, count);
+
+ /* if flag is BTMTK_FW_OWNING not set driver own , because data is fw own event */
+ if (data != NULL && (count > 1 || data[0] != 0x00) && cif_dev->own_state != BTMTK_FW_OWNING) {
+ atomic_set(&cif_dev->need_drv_own, 1);
+ atomic_set(&cif_dev->fw_wake, 1);
+ wake_up_interruptible(&tx_wait_q);
+ }
+#endif
+
+ /* add hci device part */
+ ret = btmtk_recv(bdev->hdev, data, count);
+ if (ret < 0)
+ BTMTK_ERR("%s, ret = %d", __func__, ret);
+}
+
+/* btmtk_uart_tty_wakeup()
+ *
+ * Callback for transmit wakeup. Called when low level
+ * device driver can accept more send data.
+ *
+ * Arguments: tty pointer to associated tty instance data
+ * Return Value: None
+ */
+static void btmtk_uart_tty_wakeup(struct tty_struct *tty)
+{
+ BTMTK_INFO("%s: tty %p", __func__, tty);
+}
+
+#if (SLEEP_ENABLE == 0)
+static int btmtk_uart_fw_own(struct btmtk_dev *bdev)
+{
+ int ret;
+ u8 cmd[] = { 0x01, 0x6F, 0xFC, 0x05, 0x01, 0x03, 0x01, 0x00, 0x01 };
+ u8 evt[] = { 0x04, 0xE4, 0x06, 0x02, 0x03, 0x02, 0x00, 0x00, 0x01 };
+
+
+ BTMTK_INFO("%s", __func__);
+ ret = btmtk_main_send_cmd(bdev, cmd, FWOWN_CMD_LEN, evt, OWNTYPE_EVT_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ return ret;
+}
+
+static int btmtk_uart_driver_own(struct btmtk_dev *bdev)
+{
+ int ret;
+ u8 cmd[] = { 0xFF };
+ u8 evt[] = { 0x04, 0xE4, 0x06, 0x02, 0x03, 0x02, 0x00, 0x00, 0x03 };
+
+ BTMTK_INFO("%s", __func__);
+ ret = btmtk_main_send_cmd(bdev, cmd, DRVOWN_CMD_LEN, evt, OWNTYPE_EVT_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+
+ return ret;
+}
+
+#else //(SLEEP_ENABLE == 1)
+static int btmtk_uart_fw_own(struct btmtk_dev *bdev)
+{
+ int ret = 0;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ u8 cmd[] = { 0x01, 0x6F, 0xFC, 0x06, 0x01, 0x03, 0x02, 0x00, 0x01, 0x01 };
+ u8 evt[] = { 0x04, 0xE4, 0x07, 0x02, 0x03, 0x03, 0x00, 0x00, 0x01, 0x01 };
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ UART_OWN_MUTEX_LOCK();
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ /* no need to compare BTMTK_FW_OWNING because the state must be fw_own/fail before leaving mutex */
+ if (cif_dev->own_state == BTMTK_FW_OWN || cif_dev->own_state == BTMTK_OWN_FAIL) {
+ BTMTK_WARN("Already at fw own state or error state[%d], skip", cif_dev->own_state);
+ goto unlock;
+ }
+
+ cif_dev->own_state = BTMTK_FW_OWNING;
+
+ if (cif_dev->sleep_en) {
+ ret = btmtk_main_send_cmd(bdev, cmd, FWOWN_CMD_LEN, evt, OWNTYPE_EVT_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_PKT_SEND_DIRECT);
+ } else
+ ret = 0;
+
+ if (ret < 0) {
+ BTMTK_ERR("%s failed!!", __func__);
+ cif_dev->own_state = BTMTK_OWN_FAIL;
+ goto unlock;
+ } else {
+ cif_dev->own_state = BTMTK_FW_OWN;
+ atomic_set(&cif_dev->fw_wake, 0);
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ /* Clr TX,RX request, let uarthub can sleep */
+ if (cif_dev->hub_en && cif_dev->sleep_en) {
+ ret = mtk8250_uart_hub_clear_request();
+ if (ret)
+ BTMTK_ERR("%s mtk8250_uart_hub_clear_request fail ret[%d]", __func__, ret);
+ }
+#endif
+ BTMTK_INFO("%s success", __func__);
+ }
+unlock:
+ UART_OWN_MUTEX_UNLOCK();
+ return ret;
+}
+
+static int btmtk_uart_driver_own(struct btmtk_dev *bdev)
+{
+ int ret = 0, retry = 5;
+ struct btmtk_uart_dev *cif_dev = NULL;
+ u8 wakeup_cmd[] = { 0xFF };
+ u8 fw_own_clr_cmd[] = { 0x01, 0x6F, 0xFC, 0x06, 0x01, 0x03, 0x02, 0x00, 0x03, 0x01 };
+ u8 evt[] = { 0x04, 0xE4, 0x07, 0x02, 0x03, 0x03, 0x00, 0x00, 0x03, 0x01 };
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+
+ UART_OWN_MUTEX_LOCK();
+ cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ if (cif_dev->own_state == BTMTK_DRV_OWN || cif_dev->own_state == BTMTK_OWN_FAIL) {
+ //BTMTK_WARN("Already at driver own state or error state[%d], skip", cif_dev->own_state);
+ btmtk_uart_update_fw_own_timer(cif_dev);
+ goto unlock;
+ }
+
+ cif_dev->own_state = BTMTK_DRV_OWNING;
+
+#if IS_ENABLED(CONFIG_MTK_UARTHUB)
+ if (cif_dev->hub_en && cif_dev->sleep_en) {
+ ret = btmtk_wakeup_uarthub();
+ if (ret < 0) {
+ BTMTK_ERR("%s wakeup uart_hub fail", __func__);
+ cif_dev->own_state = BTMTK_OWN_FAIL;
+ goto unlock;
+ }
+ }
+#endif
+
+ if (cif_dev->sleep_en) {
+ do {
+ /* if fw already wake, no need to send 0xFF and wait 5ms before clr fw own */
+ if (!atomic_read(&cif_dev->fw_wake)){
+ /* no need to wait event */
+ ret = btmtk_main_send_cmd(bdev, wakeup_cmd, DRVOWN_CMD_LEN, NULL, 0,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_PKT_SEND_DIRECT);
+ if (ret < 0)
+ BTMTK_ERR("%s wakeup_cmd fail retry[%d]", __func__, retry);
+ /* wait a while for fw wakeup */
+ usleep_range(5000, 5100);
+ }
+ /* fw own clr cmd for notice is wakeup by bt driver */
+ ret = btmtk_main_send_cmd(bdev, fw_own_clr_cmd, 10, evt, OWNTYPE_EVT_LEN,
+ DELAY_TIMES, RETRY_TIMES, BTMTK_TX_PKT_SEND_DIRECT);
+ if (ret < 0)
+ BTMTK_ERR("%s fw_own_clr_cmd fail retry[%d]", __func__, retry);
+ } while (ret < 0 && --retry);
+ } else
+ ret = 0;
+
+ if (ret < 0) {
+ BTMTK_ERR("%s fail", __func__);
+ cif_dev->own_state = BTMTK_OWN_FAIL;
+ goto unlock;
+ } else if (cif_dev->no_fw_own == 0) {
+ btmtk_uart_update_fw_own_timer(cif_dev);
+ cif_dev->own_state = BTMTK_DRV_OWN;
+ complete(&bdev->drv_own_comp);
+ BTMTK_INFO("%s success", __func__);
+ }
+
+unlock:
+ UART_OWN_MUTEX_UNLOCK();
+ return ret;
+}
+#endif
+
+static int btmtk_cif_probe(struct tty_struct *tty)
+{
+ int ret = -1;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev;
+#if (USE_DEVICE_NODE == 1)
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+#endif
+
+ /* Mediatek Driver Version */
+ BTMTK_INFO("%s: MTK BT Driver Version: %s", __func__, VERSION);
+
+ /* Retrieve priv data and set to interface structure */
+ bdev = btmtk_get_dev();
+ if (!bdev) {
+ BTMTK_INFO("%s: bdev is NULL", __func__);
+ return -ENODEV;
+ }
+
+ cif_dev = devm_kzalloc(tty->dev, sizeof(*cif_dev), GFP_KERNEL);
+ if (!cif_dev)
+ return -ENOMEM;
+
+ bdev->intf_dev = tty->dev;
+ bdev->cif_dev = cif_dev;
+ dev_set_drvdata(tty->dev, bdev);
+ g_tty = tty;
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_PROBE;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Init completion */
+ init_completion(&bdev->dump_comp);
+ init_completion(&bdev->drv_own_comp);
+
+ /* Init semaphore */
+ sema_init(&cif_dev->evt_comp_sem, 1);
+
+ /* Do HIF events */
+ ret = btmtk_uart_tty_probe(tty);
+#if (USE_DEVICE_NODE == 1)
+ btmtk_connv3_sub_drv_init(bdev);
+ btmtk_pwrctrl_register_evt();
+
+ /* Init coredump */
+ bmain_info->hif_hook.coredump_handler = connv3_coredump_init(CONNV3_DEBUG_TYPE_BT, NULL);
+#endif
+ return ret;
+}
+
+static void btmtk_cif_disconnect(struct tty_struct *tty)
+{
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_uart_dev *cif_dev;
+
+ bdev = dev_get_drvdata(tty->dev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return;
+ }
+
+ cif_dev = bdev->cif_dev;
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_DISCONNECT;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s priv setting is NULL", __func__);
+ return;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ btmtk_uart_cif_mutex_lock(bdev);
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* temp solution for disconnect at random time would KE */
+ BTMTK_INFO("%s wait", __func__);
+ msleep(3000);
+
+ /* Do HIF events */
+ btmtk_uart_tty_disconnect(tty);
+
+ /* Set End/Error state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ btmtk_uart_cif_mutex_unlock(bdev);
+ devm_kfree(tty->dev, cif_dev);
+
+ BTMTK_INFO("%s end", __func__);
+}
+
+static int btmtk_cif_suspend(void)
+{
+ int cif_event = 0, state, ret = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct tty_struct *tty = g_tty;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ unsigned char fstate = BTMTK_FOPS_STATE_INIT;
+#if (USE_DEVICE_NODE == 0)
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ struct btmtk_woble *bt_woble = &cif_dev->bt_woble;
+#endif
+ BTMTK_INFO("%s", __func__);
+
+ if (tty == NULL) {
+ BTMTK_ERR("%s: tty is NULL, maybe not run btmtk_cif_probe yet", __func__);
+ return -EAGAIN;
+ }
+
+ bdev = dev_get_drvdata(tty->dev);
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -EAGAIN;
+ }
+
+ if (bdev->get_hci_reset) {
+ BTMTK_WARN("open flow not ready(%d), retry", bdev->get_hci_reset);
+ return -EAGAIN;
+ }
+
+ if (bmain_info->reset_stack_flag) {
+ BTMTK_WARN("reset stack flag(%d), retry", bmain_info->reset_stack_flag);
+ return -EAGAIN;
+ }
+
+ fstate = btmtk_fops_get_state(bdev);
+ if ((fstate == BTMTK_FOPS_STATE_CLOSING) ||
+ (fstate == BTMTK_FOPS_STATE_OPENING)) {
+ BTMTK_WARN("%s: fops open/close is on-going, retry", __func__);
+ return -EAGAIN;
+ }
+
+ if (bdev->suspend_count++) {
+ BTMTK_WARN("Has suspended. suspend_count: %d, end", bdev->suspend_count);
+ return 0;
+ }
+
+ state = btmtk_get_chip_state(bdev);
+ /* Retrieve current HIF event state */
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't dos suspend flow!!!", __func__);
+ cif_event = HIF_EVENT_FW_DUMP;
+ } else
+ cif_event = HIF_EVENT_SUSPEND;
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if (USE_DEVICE_NODE == 0) //temp for resolve fw/driver own fail in sp
+ ret = btmtk_woble_suspend(bt_woble);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_suspend return fail %d", __func__, ret);
+
+
+ ret = btmtk_uart_fw_own(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s: set fw own return fail %d", __func__, ret);
+#endif
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ return 0;
+}
+
+static int btmtk_cif_resume(void)
+{
+ struct tty_struct *tty = g_tty;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_cif_state *cif_state = NULL;
+ int ret = 0;
+
+#if (USE_DEVICE_NODE == 0)
+ struct btmtk_uart_dev *cif_dev = (struct btmtk_uart_dev *)bdev->cif_dev;
+ struct btmtk_woble *bt_woble = &cif_dev->bt_woble;
+#endif
+
+ BTMTK_INFO("%s", __func__);
+
+ if (tty == NULL) {
+ BTMTK_ERR("%s: tty is NULL, maybe not run btmtk_cif_probe yet", __func__);
+ return -EAGAIN;
+ }
+
+ bdev = dev_get_drvdata(tty->dev);
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev is NULL", __func__);
+ return -1;
+ }
+ bdev->suspend_count--;
+
+ if (bdev->suspend_count) {
+ BTMTK_INFO("data->suspend_count %d, return 0", bdev->suspend_count);
+ return 0;
+ }
+
+ cif_state = &bdev->cif_state[HIF_EVENT_RESUME];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+#if (USE_DEVICE_NODE == 0) //temp for resolve fw/driver own fail in sp
+ ret = btmtk_uart_driver_own(bdev);
+ if (ret < 0)
+ BTMTK_ERR("%s: set driver own return fail %d", __func__, ret);
+
+ ret = btmtk_woble_resume(bt_woble);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_resume return fail %d", __func__, ret);
+#endif
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ return 0;
+}
+
+static int btmtk_pm_notification(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ int retry = 40;
+ int ret = NOTIFY_DONE;
+
+ BTMTK_DBG("event = %ld", event);
+
+ /* if get into suspend flow while doing audio pinmux setting
+ * it may have chance mischange uart pinmux we want to write
+ * retry and wait audio setting done then do suspend flow
+ */
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ do {
+ ret = btmtk_cif_suspend();
+ if (ret == 0) {
+ break;
+ } else if (retry <= 0) {
+ BTMTK_ERR("not ready to suspend");
+#if (USE_DEVICE_NODE == 0)
+ return NOTIFY_STOP;
+#else
+ return NOTIFY_BAD;
+#endif
+ }
+ msleep(50);
+ } while (retry-- > 0);
+ break;
+ case PM_POST_SUSPEND:
+ btmtk_cif_resume();
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static struct notifier_block btmtk_pm_notifier = {
+ .notifier_call = btmtk_pm_notification,
+};
+
+static int uart_register(void)
+{
+ u32 err = 0;
+
+ BTMTK_INFO("%s", __func__);
+
+ /* Register the tty discipline */
+ memset(&btmtk_uart_ldisc, 0, sizeof(btmtk_uart_ldisc));
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ btmtk_uart_ldisc.magic = TTY_LDISC_MAGIC;
+#else
+ btmtk_uart_ldisc.num = N_MTK;
+#endif
+ btmtk_uart_ldisc.name = "n_mtk";
+ btmtk_uart_ldisc.open = btmtk_cif_probe;
+ btmtk_uart_ldisc.close = btmtk_cif_disconnect;
+ btmtk_uart_ldisc.read = btmtk_uart_tty_read;
+ btmtk_uart_ldisc.write = btmtk_uart_tty_write;
+ btmtk_uart_ldisc.ioctl = btmtk_uart_tty_ioctl;
+ btmtk_uart_ldisc.compat_ioctl = btmtk_uart_tty_compat_ioctl;
+ btmtk_uart_ldisc.poll = btmtk_uart_tty_poll;
+ btmtk_uart_ldisc.receive_buf = btmtk_uart_tty_receive;
+ btmtk_uart_ldisc.write_wakeup = btmtk_uart_tty_wakeup;
+ btmtk_uart_ldisc.owner = THIS_MODULE;
+
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ err = tty_register_ldisc(N_MTK, &btmtk_uart_ldisc);
+#else
+ err = tty_register_ldisc(&btmtk_uart_ldisc);
+#endif
+ if (err) {
+ BTMTK_ERR("MTK line discipline registration failed. (%d)", err);
+ return err;
+ }
+ err = register_pm_notifier(&btmtk_pm_notifier);
+ if (err) {
+ BTMTK_ERR("Register pm notifier failed. (%d)", err);
+ return err;
+ }
+
+ BTMTK_INFO("%s done", __func__);
+ return err;
+}
+static int uart_deregister(void)
+{
+ u32 err = 0;
+
+ err = unregister_pm_notifier(&btmtk_pm_notifier);
+ if (err) {
+ BTMTK_ERR("Unregister pm notifier failed. (%d)", err);
+ return err;
+ }
+
+#if (defined(ANDROID_OS) && (KERNEL_VERSION(5, 15, 0) > LINUX_VERSION_CODE)) || defined(LINUX_OS)
+ err = tty_unregister_ldisc(N_MTK);
+ if (err) {
+ BTMTK_ERR("line discipline registration failed. (%d)", err);
+ return err;
+ }
+#else
+ tty_unregister_ldisc(&btmtk_uart_ldisc);
+#endif
+
+ return 0;
+}
+
+int btmtk_cif_register(void)
+{
+ int ret = -1;
+ struct hif_hook_ptr hook;
+
+ BTMTK_INFO("%s", __func__);
+
+ memset(&hook, 0, sizeof(hook));
+#if (USE_DEVICE_NODE == 1)
+ hook.fw_log_state = fw_log_bt_state_cb;
+ hook.log_init = btmtk_connsys_log_init;
+ hook.log_read_to_user = btmtk_connsys_log_read_to_user;
+ hook.log_get_buf_size = btmtk_connsys_log_get_buf_size;
+ hook.log_deinit = btmtk_connsys_log_deinit;
+ hook.log_handler = btmtk_connsys_log_handler;
+ hook.init = btmtk_chardev_init;
+ hook.dump_debug_sop = btmtk_uart_sp_dump_debug_sop;
+#endif
+ hook.open = btmtk_uart_open;
+ hook.close = btmtk_uart_close;
+ hook.pre_open = btmtk_uart_pre_open;
+ hook.open_done = btmtk_uart_open_done;
+ hook.reg_read = btmtk_uart_read_register;
+ hook.send_cmd = btmtk_uart_send_cmd;
+ hook.send_and_recv = btmtk_uart_send_and_recv;
+ hook.event_filter = btmtk_uart_event_filter;
+ hook.subsys_reset = btmtk_uart_subsys_reset;
+ hook.chip_reset_notify = btmtk_uart_chip_reset_notify;
+ hook.cif_mutex_lock = btmtk_uart_cif_mutex_lock;
+ hook.cif_mutex_unlock = btmtk_uart_cif_mutex_unlock;
+ hook.dl_dma = btmtk_uart_load_fw_patch_using_dma;
+ hook.waker_notify = btmtk_uart_waker_notify;
+ hook.set_para= btmtk_uart_set_para;
+ btmtk_reg_hif_hook(&hook);
+
+ ret = uart_register();
+ if (ret < 0) {
+ BTMTK_ERR("*** UART registration fail(%d)! ***", ret);
+ return ret;
+ }
+
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+}
+
+int btmtk_cif_deregister(void)
+{
+ int ret = -1;
+
+ BTMTK_INFO("%s", __func__);
+ ret = uart_deregister();
+ if (ret < 0) {
+ BTMTK_ERR("*** UART deregistration fail(%d)! ***", ret);
+ return ret;
+ }
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+}
+
+#if (USE_DEVICE_NODE == 1)
+void btmtk_connsys_log_init(void (*log_event_cb)(void))
+{
+ if (connv3_log_init(CONNV3_DEBUG_TYPE_BT, 2048, 2048, log_event_cb))
+ BTMTK_ERR("*** %s fail! ***", __func__);
+}
+
+void btmtk_connsys_log_deinit(void)
+{
+ connv3_log_deinit(CONNV3_DEBUG_TYPE_BT);
+}
+
+int btmtk_connsys_log_handler(u8 *buf, u32 size)
+{
+ return connv3_log_handler(CONNV3_DEBUG_TYPE_BT, CONNV3_LOG_TYPE_PRIMARY, buf, size);
+}
+
+ssize_t btmtk_connsys_log_read_to_user(char __user *buf, size_t count)
+{
+ return connv3_log_read_to_user(CONNV3_DEBUG_TYPE_BT, buf, count);
+}
+
+unsigned int btmtk_connsys_log_get_buf_size(void)
+{
+ return connv3_log_get_buf_size(CONNV3_DEBUG_TYPE_BT);
+}
+#endif
diff --git a/uart_daemon/Android.mk b/uart_daemon/Android.mk
new file mode 100755
index 0000000..0fb174c
--- /dev/null
+++ b/uart_daemon/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+$(info [BT_Drv] uart_launcher)
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS += -Wall -Werror -Wno-error=date-time
+
+LOCAL_SHARED_LIBRARIES := libcutils liblog libdl
+LOCAL_HEADER_LIBRARIES := libcutils_headers
+LOCAL_SRC_FILES := uart_launcher.c
+LOCAL_MODULE := uart_launcher
+LOCAL_PROPRIETARY_MODULE := true
+LOCAL_MODULE_OWNER := mtk
+LOCAL_MODULE_TAGS := optional
+include $(MTK_EXECUTABLE)
diff --git a/uart_daemon/Makefile b/uart_daemon/Makefile
new file mode 100755
index 0000000..26bb445
--- /dev/null
+++ b/uart_daemon/Makefile
@@ -0,0 +1,36 @@
+#---------------------------------------------------------------------------
+TARGET = uart_launcher
+
+OBJECT = \
+ uart_launcher.o \
+ $(NULL)
+
+CFLAGS = \
+ -g \
+ -Wall \
+ -Werror \
+ $(NULL)
+
+#---------------------------------------------------------------------------
+ifeq ($(CROSS_COMPILE),)
+ CC = gcc
+ STRIP = strip
+else
+ CC = $(CROSS_COMPILE)-gcc
+ STRIP = $(CROSS_COMPILE)-strip
+endif
+
+#---------------------------------------------------------------------------
+all: uart_launcher
+
+uart_launcher: clean $(OBJECT)
+ $(CC) -pthread -C -o $(TARGET) $(OBJECT)
+ $(STRIP) $(TARGET)
+
+%.o: %.c
+ $(CC) $(LINKFLAGS) $(CFLAGS) $(INCLUDE) -c -o $@ $<
+
+clean:
+ rm -f $(TARGET) *.o
+
+#---------------------------------------------------------------------------
diff --git a/uart_daemon/uart_launcher.c b/uart_daemon/uart_launcher.c
new file mode 100755
index 0000000..38fdd8a
--- /dev/null
+++ b/uart_daemon/uart_launcher.c
@@ -0,0 +1,474 @@
+/* Copyright Statement:
+ *
+ * This software/firmware and related documentation ("MediaTek Software") are
+ * protected under relevant copyright laws. The information contained herein
+ * is confidential and proprietary to MediaTek Inc. and/or its licensors.
+ * Without the prior written permission of MediaTek inc. and/or its licensors,
+ * any reproduction, modification, use or disclosure of MediaTek Software,
+ * and information contained herein, in whole or in part, shall be strictly prohibited.
+ *
+ * MediaTek Inc. (C) 2010. All rights reserved.
+ *
+ * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+ * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+ * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
+ * AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+ * NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+ * SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+ * SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
+ * THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
+ * THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
+ * CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
+ * SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
+ * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
+ * CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+ * AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+ * OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
+ * MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+ *
+ * The following software/firmware and/or related documentation ("MediaTek Software")
+ * have been modified by MediaTek Inc. All revisions are subject to any receiver's
+ * applicable license agreements with MediaTek Inc.
+ */
+
+/******************************************************************************
+* C O M P I L E R F L A G S
+*******************************************************************************
+*/
+
+/******************************************************************************
+* E X T E R N A L R E F E R E N C E S
+*******************************************************************************
+*/
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+//#include <syslog.h>
+#include <termios.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <linux/serial.h> /* struct serial_struct */
+#include "uart_launcher.h"
+
+//---------------------------------------------------------------------------
+static int set_speed(int fd, struct termios *ti, int speed);
+int setup_uart_param (int hComPort, int iBaudrate, struct UART_CONFIG *sUartConfig);
+
+static int gTtyFd = -1;
+static int cont = 1; /** loop continue running */
+
+//---------------------------------------------------------------------------
+/* Used as host uart param setup callback */
+int setup_uart_param (
+ int hComPort,
+ int iBaudrate,
+ struct UART_CONFIG *sUartConfig)
+{
+ struct termios ti;
+ int fd;
+ BPRINT_I("setup_uart_param begin");
+ if(!sUartConfig){
+ BPRINT_E("Invalid sUartConfig");
+ return -2;
+ }
+
+ BPRINT_I("setup_uart_param Baud %d FC %d", iBaudrate, sUartConfig->fc);
+
+ fd = hComPort;
+ if (fd < 0) {
+ BPRINT_E("Invalid serial port");
+ return -2;
+ }
+
+ tcflush(fd, TCIOFLUSH);
+
+ if (tcgetattr(fd, &ti) < 0) {
+ BPRINT_E("Can't get port settings");
+ return -3;
+ }
+
+ cfmakeraw(&ti);
+
+ BPRINT_I("ti.c_cflag = 0x%08x", ti.c_cflag);
+ ti.c_cflag |= CLOCAL;
+ BPRINT_I("CLOCAL = 0x%x", CLOCAL);
+ BPRINT_I("(ori)ti.c_iflag = 0x%08x", ti.c_iflag);
+ BPRINT_I("(ori)ti.c_cflag = 0x%08x", ti.c_cflag);
+ BPRINT_I("sUartConfig->fc= %d (0:none,sw,hw,linux)", sUartConfig->fc);
+
+ switch (sUartConfig->fc) {
+ /* HW FC Enable */
+ case UART_HW_FC:
+ ti.c_cflag |= CRTSCTS;
+ ti.c_iflag &= ~(NOFLSH);
+ break;
+ /* Linux Software FC */
+ case UART_LINUX_FC:
+ ti.c_iflag |= (IXON | IXOFF | IXANY);
+ ti.c_cflag &= ~(CRTSCTS);
+ ti.c_iflag &= ~(NOFLSH);
+ break;
+ /* MTK Software FC */
+ case UART_MTK_SW_FC:
+ ti.c_iflag |= CRTSCTS;
+ ti.c_cflag &= ~(NOFLSH);
+ break;
+ /* default disable flow control */
+ default:
+ ti.c_cflag &= ~(CRTSCTS);
+ ti.c_iflag &= ~(NOFLSH|CRTSCTS);
+ }
+
+ BPRINT_D("c_c CRTSCTS = 0x%16x", CRTSCTS);
+ BPRINT_D("c_i IXON = 0x%08x", IXON);
+ BPRINT_D("c_i IXOFF = 0x%08x", IXOFF);
+ BPRINT_D("c_i IXANY = 0x%08x", IXANY);
+ BPRINT_D("(aft)ti.c_iflag = 0x%08x", ti.c_iflag);
+ BPRINT_D("(aft)ti.c_cflag = 0x%08x", ti.c_cflag);
+
+ if (tcsetattr(fd, TCSANOW, &ti) < 0) {
+ BPRINT_E("Can't set port settings");
+ return -4;
+ }
+
+ /* Set baudrate */
+ if (set_speed(fd, &ti, iBaudrate) < 0) {
+ BPRINT_E("Can't set initial baud rate");
+ return -5;
+ }
+
+ tcflush(fd, TCIOFLUSH);
+ BPRINT_I("%s done", __func__);
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static speed_t get_speed (int baudrate)
+{
+ unsigned int idx;
+ for (idx = 0; idx < sizeof(speeds)/sizeof(speeds[0]); idx++) {
+ if (baudrate == (int)speeds[idx].baud) {
+ return speeds[idx].speed;
+ }
+ }
+ return CBAUDEX;
+}
+
+//---------------------------------------------------------------------------
+int set_speed(int fd, struct termios *ti, int speed)
+{
+ struct serial_struct ss;
+ int baudenum = get_speed(speed);
+
+ if (speed != CBAUDEX) {
+ //printf("%s: standard baudrate: %d -> 0x%08x\n", __FUNCTION__, speed, baudenum);
+ if ((ioctl(fd, TIOCGSERIAL, &ss)) < 0) {
+ BPRINT_E("%s: BAUD: error to get the serial_struct info:%s\n", __func__, strerror(errno));
+ return -1;
+ }
+#ifdef ANDROID
+ ss.flags &= ~ASYNC_SPD_CUST;
+#if defined(SERIAL_STRUCT_EXT) /*modified in serial_struct.h*/
+ memset(ss.reserved, 0x00, sizeof(ss.reserved));
+#endif
+ ss.flags |= (1 << 13); /*set UPFLOWLATENCY flat to tty, or serial_core will reset tty->low_latency to 0*/
+ /*set standard buadrate setting*/
+ if ((ioctl(fd, TIOCSSERIAL, &ss)) < 0) {
+ BPRINT_E("%s: BAUD: error to set serial_struct:%s\n", __func__, strerror(errno));
+ return -2;
+ }
+#endif
+ cfsetospeed(ti, baudenum);
+ cfsetispeed(ti, baudenum);
+ return tcsetattr(fd, TCSANOW, ti);
+ }
+ else {
+ BPRINT_E("%s: unsupported non-standard baudrate: %d -> 0x%08x\n", __func__, speed, baudenum);
+ return -3;
+ }
+}
+
+//---------------------------------------------------------------------------
+void init_flock(struct flock *flk)
+{
+ flk->l_type = F_WRLCK;
+ flk->l_whence = SEEK_SET;
+ flk->l_pid = getpid();
+ flk->l_start = 0;
+ flk->l_len = 0;
+}
+
+void unlock_flock(int fd, struct flock *fl, int type, int whence)
+{
+ fl->l_type = type;
+ fl->l_whence = whence;
+ if (fcntl(fd, F_SETLKW, fl) < 0)
+ BPRINT_E("%s: fcntl failed(%d)", __func__, errno);
+}
+
+//---------------------------------------------------------------------------
+int cmd_hdr_baud (struct UART_CONFIG *sUartConfig, int baudrate) {
+ return (gTtyFd != -1) ? setup_uart_param(gTtyFd, baudrate, sUartConfig) : -1;
+}
+
+//---------------------------------------------------------------------------
+int osi_system(const char *cmd)
+{
+ FILE *fp;
+ int ret;
+
+ if (cmd == NULL) {
+ BPRINT_E("%s: cmd is NULL", __func__);
+ return -1;
+ }
+
+ fp = popen(cmd, "w");
+ if (fp == NULL) {
+ BPRINT_E("%s: (%s) failed", __func__, cmd);
+ return -1;
+ }
+
+ BPRINT_I("Command: %s", cmd);
+
+ ret = pclose(fp);
+ if (ret < 0) {
+ BPRINT_E("%s: pclose ret = %d", __func__, ret);
+ } else if (ret > 0) {
+ BPRINT_I("%s: pclose ret = %d", __func__, ret);
+ }
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static void uart_launcher_sig_handler(int signum)
+{
+ BPRINT_I("%s: %d", __func__, signum);
+ cont = 0;
+}
+
+//---------------------------------------------------------------------------
+int main(int argc, char *argv[])
+{
+ BPRINT_I("Bluetooth Uart Launcher Ver %s", VERSION);
+ int ld = 0;
+ struct pollfd fds;
+ int fd_num = 0;
+ int err = 0;
+ int opt;
+ int baudrate = 115200;
+ int chang_baud_rate = 0;
+ int retry = 0;
+ int flow_control = UART_DISABLE_FC;
+ char *tty_path = "/dev/ttyUSB0";
+ struct UART_CONFIG sUartConfig;
+ struct sigaction sigact;
+ struct flock fl;
+
+ memset(&sUartConfig, 0, sizeof(struct UART_CONFIG));
+ memset(&fds, 0, sizeof(struct pollfd));
+ sUartConfig.fc = flow_control;
+
+ /* Register signal handler */
+ sigact.sa_handler = uart_launcher_sig_handler;
+ sigact.sa_flags = 0;
+ sigemptyset(&sigact.sa_mask);
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGKILL, &sigact, NULL);
+ init_flock(&fl);
+ ld = N_MTK;
+
+ while ((opt = getopt(argc, argv, "c:f:p:k:l::")) != -1) {
+ switch (opt) {
+ /* change baudrate */
+ case 'c':
+ baudrate = atoi(optarg);
+ BPRINT_I("baudrate = %d", baudrate);
+ /* not need to change if baudrate is same */
+ if (baudrate == CUST_BAUDRATE_DFT) {
+ chang_baud_rate = 0;
+ } else
+ chang_baud_rate = 1;
+ break;
+ /* flow control */
+ case 'f':
+ flow_control = atoi(optarg);
+ BPRINT_I("flow_control = %d", flow_control);
+ break;
+ /* tty path */
+ case 'p':
+ tty_path = optarg;
+ BPRINT_I("Log path is %s", tty_path);
+ break;
+ /* kill process */
+ case 'k':
+ osi_system("killall uart_launcher");
+ BPRINT_I("Kill uart_launcher");
+ return 0;
+ /* set ldisc */
+ case 'l':
+ ld = atoi(optarg);
+ BPRINT_I("set ldisc[%d]", ld);
+ break;
+ case '?':
+ default:
+ BPRINT_I("set baud:\t uart_launcher -c [baudrate]");
+ BPRINT_I("flow control:\t uart_launcher -f [flow_control]");
+ BPRINT_I("\t\t 0:disable FC 1:MTK_SW_FC 2: SW_FC 3: HW_FC");
+ BPRINT_I("tty path:\t uart_launcher -p [path]");
+ BPRINT_I("kill process:\t uart_launcher -k");
+ goto exit;
+ break;
+ }
+ }
+
+ /* open ttyUSB */
+ BPRINT_I("Running...");
+ /* node may not ready, retry 20 times */
+ while (1) {
+ gTtyFd = open(tty_path, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ BPRINT_I("open done ttyfd %d", gTtyFd);
+ if (gTtyFd < 0) {
+ if (retry > 20) {
+ BPRINT_E("ttyfd %d, error", gTtyFd);
+ goto exit;
+ } else {
+ retry++;
+ (void)usleep(1000 * 1000);
+ }
+ } else
+ break;
+ }
+
+ /* flock the device node */
+ BPRINT_I("flock the device node");
+ if (fcntl(gTtyFd, F_SETLK, &fl) < 0) {
+ BPRINT_E("lock device node failed, uart_launcher already running.");
+ goto exit;
+ }
+
+ /* to ensure driver register TIOCSETD, retry 20 times */
+ retry = 0;
+ while (1) {
+ if (ioctl(gTtyFd, TIOCSETD, &ld) < 0) {
+ if (retry > 20) {
+ BPRINT_E("set TIOCSETD N_MTK error");
+ goto exit;
+ } else {
+ retry++;
+ (void)usleep(1000 * 1000);
+ }
+ } else
+ break;
+ }
+
+restart:
+ /* Set default Baud rate */
+ sUartConfig.iBaudrate = CUST_BAUDRATE_DFT;
+ BPRINT_I("set baudtate = %d", CUST_BAUDRATE_DFT);
+ cmd_hdr_baud(&sUartConfig, CUST_BAUDRATE_DFT);
+ fds.fd = gTtyFd;
+ fds.events = POLLIN;
+ ++fd_num;
+
+ err = ioctl(gTtyFd, HCIUARTINIT, NULL);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTINIT error %d", err);
+ return 0;
+ }
+
+ err = ioctl(gTtyFd, HCIUARTGETBAUD, NULL);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTGETBAUD error %d", err);
+ return 0;
+ }
+
+ /* chang baud rate */
+ if (chang_baud_rate | flow_control) {
+ sUartConfig.iBaudrate = baudrate;
+ sUartConfig.fc = flow_control;
+ err = ioctl(gTtyFd, HCIUARTSETBAUD, &sUartConfig);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTSETBAUD error %d", err);
+ return 0;
+ }
+
+ BPRINT_I("set baudtate %d", baudrate);
+ cmd_hdr_baud(&sUartConfig, baudrate);
+
+ err = ioctl(gTtyFd, HCIUARTSETWAKEUP, NULL);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTSETWAKEUP error %d", err);
+ return 0;
+ }
+ }
+
+ if (ioctl(gTtyFd, HCIUARTLOADPATCH, NULL) < 0) {
+ BPRINT_E("set HCIUARTLOADPATCH error");
+ return 0;
+ }
+
+ while (cont) {
+ err = poll(&fds, fd_num, 2000);
+ if (err < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ else {
+ BPRINT_E("poll error:%d errno:%d, %s", err, errno, strerror(errno));
+ break;
+ }
+ } else if (!err) {
+ if (fds.revents & POLLIN) {
+ goto restart;
+ } else {
+ continue;
+ }
+ }
+ goto restart;
+ }
+
+ /* before exit daemon, return baud to default */
+ if (chang_baud_rate | flow_control) {
+ sUartConfig.iBaudrate = CUST_BAUDRATE_DFT;
+ sUartConfig.fc = UART_DISABLE_FC;
+ err = ioctl(gTtyFd, HCIUARTSETBAUD, &sUartConfig);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTSETBAUD error %d", err);
+ return 0;
+ }
+
+ BPRINT_I("set baudtate %d", CUST_BAUDRATE_DFT);
+ cmd_hdr_baud(&sUartConfig, CUST_BAUDRATE_DFT);
+
+ err = ioctl(gTtyFd, HCIUARTSETWAKEUP, NULL);
+ if (err < 0) {
+ BPRINT_E("set HCIUARTSETWAKEUP error %d", err);
+ return 0;
+ }
+ }
+
+exit:
+ /* unlock ttyFd */
+ if (gTtyFd > 0) {
+ BPRINT_I("unlock_flock");
+ unlock_flock(gTtyFd, &fl, F_UNLCK, SEEK_SET);
+ close(gTtyFd);
+ }
+ BPRINT_I("uart_launcher stop");
+ return 0;
+}
+//---------------------------------------------------------------------------
diff --git a/uart_daemon/uart_launcher.h b/uart_daemon/uart_launcher.h
new file mode 100755
index 0000000..0827f28
--- /dev/null
+++ b/uart_daemon/uart_launcher.h
@@ -0,0 +1,133 @@
+/* Copyright Statement:
+ *
+ * This software/firmware and related documentation ("MediaTek Software") are
+ * protected under relevant copyright laws. The information contained herein
+ * is confidential and proprietary to MediaTek Inc. and/or its licensors.
+ * Without the prior written permission of MediaTek inc. and/or its licensors,
+ * any reproduction, modification, use or disclosure of MediaTek Software,
+ * and information contained herein, in whole or in part, shall be strictly prohibited.
+ *
+ * MediaTek Inc. (C) 2010. All rights reserved.
+ *
+ * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
+ * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
+ * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
+ * AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
+ * NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
+ * SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
+ * SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
+ * THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
+ * THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
+ * CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
+ * SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
+ * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
+ * CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
+ * AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
+ * OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
+ * MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
+ */
+
+
+#ifndef __UART_LAUNCHER_H__
+#define __UART_LAUNCHER_H__
+
+#define VERSION "1.0.2021052201"
+
+#ifndef N_MTKSTP
+#define N_MTKSTP (15 + 1) /* MediaTek WCN Serial Transport Protocol */
+#endif
+
+#define N_MTK (15+1)
+#define HCIUARTSETPROTO _IOW('U', 200, int)
+#define HCIUARTSETBAUD _IOW('U', 201, int)
+#define HCIUARTGETBAUD _IOW('U', 202, int)
+#define HCIUARTSETSTP _IOW('U', 203, int)
+#define HCIUARTLOADPATCH _IOW('U', 204, int)
+#define HCIUARTSETWAKEUP _IOW('U', 205, int)
+#define HCIUARTINIT _IOW('U', 206, int)
+
+#define CUST_COMBO_WMT_DEV "/dev/stpwmt"
+#define CUST_COMBO_STP_DEV "/dev/ttyUSB0"
+#define CUST_COMBO_PATCH_PATH "/etc/firmware" //-- for ALPS
+#define LOG_TAG "uart_launcher"
+
+#define UL_MSG_LVL_DBG 3
+#define UL_MSG_LVL_INFO 2
+#define UL_MSG_LVL_ERR 1
+#define UL_MSG_LVL_NONE 0
+
+/** Debug log level */
+#define UL_MSG_LVL_DEFAULT UL_MSG_LVL_INFO
+
+#define BPRINT_D(fmt, ...) \
+ do { if (UL_MSG_LVL_DEFAULT >= UL_MSG_LVL_DBG) \
+ printf("[%s:D] "fmt"\n", LOG_TAG, ##__VA_ARGS__); } while (0);
+#define BPRINT_I(fmt, ...) \
+ do { if (UL_MSG_LVL_DEFAULT >= UL_MSG_LVL_INFO) \
+ printf("[%s] "fmt"\n", LOG_TAG, ##__VA_ARGS__); } while (0);
+#define BPRINT_E(fmt, ...) \
+ do { if (UL_MSG_LVL_DEFAULT >= UL_MSG_LVL_ERR) \
+ printf("[%s:E] "fmt" !!!\n", LOG_TAG, ##__VA_ARGS__);} while (0);
+
+#define CUST_BAUDRATE_DFT 115200
+#define CUST_MULTI_PATCH 1
+
+#ifdef CFG_MTK_SOC_CONSYS_SUPPORT
+#define CUST_MTK_SOC_CONSYS (1)
+#else
+#define CUST_MTK_SOC_CONSYS (0)
+#endif
+
+#define INIT_CMD(c, e, s) {.cmd= c, .cmd_sz=sizeof(c), .evt=e, .evt_sz=sizeof(e), .str=s}
+
+#ifndef UNUSED
+#define UNUSED(x) ((void)x)
+#endif
+
+
+typedef enum {
+ STP_MIN = 0x0,
+ STP_UART_FULL = 0x1,
+ STP_UART_MAND = 0x2,
+ STP_BTIF_FULL = 0x3,
+ STP_SDIO = 0x4,
+ STP_MAX = 0x5,
+}STP_MODE;
+
+#define MAX_CMD_LEN (NAME_MAX+1)
+
+enum UART_FC {
+ UART_DISABLE_FC = 0, /*NO flow control*/
+ /*MTK SW Flow Control, differs from Linux Flow Control*/
+ UART_MTK_SW_FC = 1,
+ UART_LINUX_FC = 2, /*Linux SW Flow Control*/
+ UART_HW_FC = 3, /*HW Flow Control*/
+};
+
+struct UART_CONFIG {
+ enum UART_FC fc;
+ int parity;
+ int stop_bit;
+ int iBaudrate;
+};
+
+struct speed_map {
+ unsigned int baud;
+ speed_t speed;
+};
+
+static struct speed_map speeds[] = {
+ {115200, B115200},
+ {921600, B921600},
+ {1000000, B1000000},
+ {1152000, B1152000},
+ {2000000, B2000000},
+ {2500000, B2500000},
+ {3000000, B3000000},
+ {3500000, B3500000},
+ {4000000, B4000000},
+};
+
+#endif /*__UART_LAUNCHER_H__*/
diff --git a/uboot_driver/MTK/mtk-bt/Makefile b/uboot_driver/MTK/mtk-bt/Makefile
new file mode 100644
index 0000000..06324c2
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/Makefile
@@ -0,0 +1,104 @@
+###############################################################################
+# Copyright Statement: #
+# #
+# This software/firmware and related documentation ("MediaTek Software") #
+# are protected under international and related jurisdictions'copyright laws #
+# as unpublished works. The information contained herein is confidential and #
+# proprietary to MediaTek Inc. Without the prior written permission of #
+# MediaTek Inc., any reproduction, modification, use or disclosure of #
+# MediaTek Software, and information contained herein, in whole or in part, #
+# shall be strictly prohibited. #
+# MediaTek Inc. Copyright (C) 2010. All rights reserved. #
+# #
+# BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND #
+# AGREES TO THE FOLLOWING: #
+# #
+# 1)Any and all intellectual property rights (including without #
+# limitation, patent, copyright, and trade secrets) in and to this #
+# Software/firmware and related documentation ("MediaTek Software") shall #
+# remain the exclusive property of MediaTek Inc. Any and all intellectual #
+# property rights (including without limitation, patent, copyright, and #
+# trade secrets) in and to any modifications and derivatives to MediaTek #
+# Software, whoever made, shall also remain the exclusive property of #
+# MediaTek Inc. Nothing herein shall be construed as any transfer of any #
+# title to any intellectual property right in MediaTek Software to Receiver. #
+# #
+# 2)This MediaTek Software Receiver received from MediaTek Inc. and/or its #
+# representatives is provided to Receiver on an "AS IS" basis only. #
+# MediaTek Inc. expressly disclaims all warranties, expressed or implied, #
+# including but not limited to any implied warranties of merchantability, #
+# non-infringement and fitness for a particular purpose and any warranties #
+# arising out of course of performance, course of dealing or usage of trade. #
+# MediaTek Inc. does not provide any warranty whatsoever with respect to the #
+# software of any third party which may be used by, incorporated in, or #
+# supplied with the MediaTek Software, and Receiver agrees to look only to #
+# such third parties for any warranty claim relating thereto. Receiver #
+# expressly acknowledges that it is Receiver's sole responsibility to obtain #
+# from any third party all proper licenses contained in or delivered with #
+# MediaTek Software. MediaTek is not responsible for any MediaTek Software #
+# releases made to Receiver's specifications or to conform to a particular #
+# standard or open forum. #
+# #
+# 3)Receiver further acknowledge that Receiver may, either presently #
+# and/or in the future, instruct MediaTek Inc. to assist it in the #
+# development and the implementation, in accordance with Receiver's designs, #
+# of certain softwares relating to Receiver's product(s) (the "Services"). #
+# Except as may be otherwise agreed to in writing, no warranties of any #
+# kind, whether express or implied, are given by MediaTek Inc. with respect #
+# to the Services provided, and the Services are provided on an "AS IS" #
+# basis. Receiver further acknowledges that the Services may contain errors #
+# that testing is important and it is solely responsible for fully testing #
+# the Services and/or derivatives thereof before they are used, sublicensed #
+# or distributed. Should there be any third party action brought against #
+# MediaTek Inc. arising out of or relating to the Services, Receiver agree #
+# to fully indemnify and hold MediaTek Inc. harmless. If the parties #
+# mutually agree to enter into or continue a business relationship or other #
+# arrangement, the terms and conditions set forth herein shall remain #
+# effective and, unless explicitly stated otherwise, shall prevail in the #
+# event of a conflict in the terms in any agreements entered into between #
+# the parties. #
+# #
+# 4)Receiver's sole and exclusive remedy and MediaTek Inc.'s entire and #
+# cumulative liability with respect to MediaTek Software released hereunder #
+# will be, at MediaTek Inc.'s sole discretion, to replace or revise the #
+# MediaTek Software at issue. #
+# #
+# 5)The transaction contemplated hereunder shall be construed in #
+# accordance with the laws of Singapore, excluding its conflict of laws #
+# principles. Any disputes, controversies or claims arising thereof and #
+# related thereto shall be settled via arbitration in Singapore, under the #
+# then current rules of the International Chamber of Commerce (ICC). The #
+# arbitration shall be conducted in English. The awards of the arbitration #
+# shall be final and binding upon both parties and shall be entered and #
+# enforceable in any court of competent jurisdiction. #
+###############################################################################
+
+THIS_COMPONENT = usb3
+
+ifdef LINUX_DRV_ROOT
+export DRV_ROOT = $(LINUX_DRV_ROOT)
+else
+export DRV_ROOT = $(TARGET_OPEN_ROOT)
+endif
+
+
+SRC =
+
+OBJ =
+
+SUB_COMPONENTS = mt7668
+
+OPTIONAL_SUB_COMPONENTS =
+
+DEFINES +=
+
+CC_INC +=
+
+
+#############################################################################
+#
+# Include the makefile common to all components
+#
+#############################################################################
+
+include $(DRV_ROOT)/driver.mak
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.c b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.c
new file mode 100644
index 0000000..098de70
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.c
@@ -0,0 +1,2708 @@
+/*
+* Copyright (c) 2014 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.
+*/
+
+//---------------------------------------------------------------------------
+#include "LD_usbbt.h"
+#include "LD_btmtk_usb.h"
+#include "LD_errno.h"
+
+
+//#include <drvPM.h>
+#define PM_SOURCE_DISABLE (0xFF)
+#define le32_to_cpu(x) ByteSwap32(x)
+#define le16_to_cpu(x) ByteSwap16(x)
+#define ByteSwap16(x) (((x) & 0x00ff) << 8 | ((x) & 0xff00) >> 8)
+#define ByteSwap32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+
+#define TOLOWER(x) ((x) | 0x20)
+#define isxdigit(c) (('0' <= (c) && (c) <= '9') || ('a' <= (c) && (c) <= 'f') || ('A' <= (c) && (c) <= 'F'))
+
+#define isdigit(c) ('0' <= (c) && (c) <= '9')
+//- Local Configuration -----------------------------------------------------
+#define LD_VERSION "3.0.21110401"
+
+#define BUFFER_SIZE (1024 * 4) /* Size of RX Queue */
+#define BT_SEND_HCI_CMD_BEFORE_SUSPEND 1
+#define LD_SUPPORT_FW_DUMP 0
+#define LD_BT_ALLOC_BUF 0
+#define LD_NOT_FIX_BUILD_WARN 0
+
+#define FIDX 0x5A /* Unify WoBLE APCF Filtering Index */
+#define BUZZARD_FIDX 0x0A /* Unify WoBLE APCF Filtering Index */
+
+
+//---------------------------------------------------------------------------
+static char driver_version[64] = { 0 };
+static unsigned char probe_counter = 0;
+static volatile int metaMode;
+static volatile int metaCount;
+/* 0: False; 1: True */
+static int isbtready;
+static int isUsbDisconnet;
+static volatile int is_assert = 0;
+static u8 u8WoBTW = PM_SOURCE_DISABLE;
+//---------------------------------------------------------------------------
+
+//-------------------------------------------------------------------------------------------------
+// extern function
+//-------------------------------------------------------------------------------------------------
+extern int snprintf(char *str, size_t size, const char *fmt, ...);
+
+static inline int is_mt7630(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76300000);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7650(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76500000);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7632(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76320000);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7662(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76620000);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7662T(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffffffff) == 0x76620100);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7632T(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffffffff) == 0x76320100);
+}
+
+//---------------------------------------------------------------------------
+static inline int is_mt7668(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7668);
+}
+
+static inline int is_mt7663(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7663);
+}
+
+static inline int is_mt7961(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7961);
+}
+
+static inline int is_mt7902(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7902);
+}
+
+static inline int is_support_unify_woble(struct LD_btmtk_usb_data *data)
+{
+ if (data->bt_cfg.support_unify_woble) {
+ if (is_mt7668(data) || is_mt7663(data) || is_mt7961(data) || is_mt7902(data))
+ return 1;
+ else
+ return 0;
+ } else {
+ return 0;
+ }
+}
+
+
+//---------------------------------------------------------------------------
+int My_strlen(char* src)
+{
+ int len = 0;
+ char *temp = src;
+ //assert(src);
+ while(*temp++ != '\0')
+ len ++;
+ return len;
+}
+
+unsigned long strtoul(const char *cp,char **endp,unsigned int base)
+{
+ unsigned long result = 0,value;
+
+ if (!base) {
+ base = 10;
+ if (*cp == '0') {
+ base = 8;
+ cp++;
+ if ((TOLOWER(*cp) == 'x') && isxdigit(cp[1])) {
+ cp++;
+ base = 16;
+ }
+ }
+ } else if (base == 16) {
+ if (cp[0] == '0' && TOLOWER(cp[1]) == 'x')
+ cp += 2;
+ }
+ while (isxdigit(*cp) &&
+ (value = isdigit(*cp) ? *cp-'0' : TOLOWER(*cp)-'a'+10) < base) {
+ result = result*base + value;
+ cp++;
+ }
+ if (endp)
+ *endp = (char *)cp;
+ return result;
+}
+long strtol(const char *cp,char **endp,unsigned int base)
+{
+ if(*cp=='-')
+ return -strtoul(cp+1,endp,base);
+ return strtoul(cp,endp,base);
+}
+
+char* My_strstr(const char* s1,const char* s2)
+{
+ int n;
+ if(*s2)
+ {
+ while(*s1)
+ {
+ for(n=0;*(s1+n)==*(s2+n);n++)
+ {
+ if(!*(s2+n+1))
+ return (char*)s1;
+ }
+ s1++;
+ }
+ return NULL;
+ }
+ else
+ return (char*)s1;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_io_read32(struct LD_btmtk_usb_data *data, u32 reg, u32 *val)
+{
+ u8 request = data->r_request;
+ int ret;
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, request,
+ DEVICE_VENDOR_REQUEST_IN, 0, (u16)reg, data->io_buf, sizeof(u32),
+ CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ *val = 0xffffffff;
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, *val);
+ return ret;
+ }
+
+ os_memmove(val, data->io_buf, sizeof(u32));
+ //*val = le32_to_cpu(*val);
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_io_read32_7xxx(struct LD_btmtk_usb_data *data, u32 reg, u32 *val)
+{
+ int ret = -1;
+ u16 reg_high;
+ u16 reg_low;
+
+ reg_high = ((reg >> 16) & 0xFFFF);
+ reg_low = (reg & 0xFFFF);
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x63,
+ DEVICE_VENDOR_REQUEST_IN, reg_high, reg_low, data->io_buf, sizeof(u32),
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ *val = 0xFFFFFFFF;
+ usb_debug("error(%d), reg=%X, value=%X\n", ret, reg, *val);
+ return ret;
+ }
+
+ os_memmove(val, data->io_buf, sizeof(u32));
+ //*val = le32_to_cpu(*val);
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_io_write32(struct LD_btmtk_usb_data *data, u32 reg, u32 val)
+{
+ u16 value, index;
+ u8 request = data->w_request;
+ mtkbt_dev_t *udev = data->udev;
+ int ret;
+
+ index = (u16) reg;
+ value = val & 0x0000ffff;
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP, request, DEVICE_VENDOR_REQUEST_OUT,
+ value, index, NULL, 0, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, val);
+ return ret;
+ }
+
+ index = (u16) (reg + 2);
+ value = (val & 0xffff0000) >> 16;
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP, request, DEVICE_VENDOR_REQUEST_OUT,
+ value, index, NULL, 0, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, val);
+ return ret;
+ }
+ if (ret > 0)
+ {
+ ret = 0;
+ }
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_wmt_cmd(struct LD_btmtk_usb_data *data, const u8 *cmd,
+ const int cmd_len, const u8 *event, const int event_len, u32 delay, u8 retry)
+{
+ int ret = -1;
+ BOOL check = FALSE;
+
+ if (!data || !data->hcif || !data->io_buf || !cmd) {
+ usb_debug("incorrect cmd pointer\n");
+ return -1;
+ }
+ if (event != NULL && event_len > 0)
+ check = TRUE;
+
+ /* send WMT command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0x01,
+ DEVICE_CLASS_REQUEST_OUT, 0x30, 0x00, (void *)cmd, cmd_len,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ if (event_len == -1) {
+ /* If event_len is -1, DO NOT read event, since FW wouldn't feedback */
+ return 0;
+ }
+
+retry_get:
+ MTK_MDELAY(delay);
+
+ /* check WMT event */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x01,
+ DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf, LD_BT_MAX_EVENT_SIZE,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("event get failed(%d)\n", ret);
+ if (check == TRUE)
+ return ret;
+ else
+ return 0;
+ }
+
+ if (check == TRUE) {
+ if (ret >= event_len && memcmp(event, data->io_buf, event_len) == 0) {
+ return ret;
+ } else if (retry > 0) {
+ usb_debug("retry to get event(%d)\n", retry);
+ retry--;
+ goto retry_get;
+ } else {
+ usb_debug("can't get expect event\n");
+ usb_debug_raw(event, event_len, "EXPECT:");
+ usb_debug_raw(data->io_buf, ret, "RCV:");
+ }
+ } else {
+ if (ret > 0) {
+ usb_debug_raw(cmd, cmd_len, "CMD:");
+ usb_debug_raw(data->io_buf, ret, "EVT:");
+ return 0;
+ } else if (retry > 0) {
+ usb_debug("retry to get event(%d)\n", retry);
+ retry--;
+ goto retry_get;
+ } else {
+ usb_debug("can't get expect event\n");
+ }
+ }
+ return -1;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_hci_cmd(struct LD_btmtk_usb_data *data, u8 *cmd,
+ const int cmd_len, const u8 *event, const int event_len)
+{
+ /** @RETURN
+ * length if event compare successfully.,
+ * 0 if doesn't check event.,
+ * < 0 if error.
+ */
+#define USB_CTRL_IO_TIMO 100
+#define USB_INTR_MSG_TIMO 2000
+ int ret = -1;
+ int len = 0;
+ int i = 0;
+ u8 retry = 0;
+ BOOL check = FALSE;
+
+ if (!data || !data->hcif || !data->io_buf || !cmd) {
+ usb_debug("incorrect cmd pointer\n");
+ return -1;
+ }
+ if (event != NULL && event_len > 0)
+ check = TRUE;
+
+ /* send HCI command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0,
+ DEVICE_CLASS_REQUEST_OUT, 0, 0, (u8 *)cmd, cmd_len, USB_CTRL_IO_TIMO);
+ if (ret < 0) {
+ usb_debug("send command failed: %d\n", ret);
+ return ret;
+ }
+
+ if (event_len == -1) {
+ /* If event_len is -1, DO NOT read event, since FW wouldn't feedback */
+ return 0;
+ }
+
+ /* check HCI event */
+ do {
+ memset(data->io_buf, 0, LD_BT_MAX_EVENT_SIZE);
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, USB_INTR_MSG_TIMO);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ if (check == TRUE) return ret;
+ else return 0;
+ }
+
+ if (check == TRUE) {
+ if (len >= event_len) {
+ for (i = 0; i < event_len; i++) {
+ //usb_debug("ret=%d i=%d reqdata data->io_buf=0x%x event=0x%x\n",ret,i,data->io_buf[i],event[i]);
+ if (event[i] != data->io_buf[i])
+ break;
+ }
+ } else {
+ usb_debug("event length is not match(%d/%d)\n", len, event_len);
+ }
+ if (i != event_len) {
+ usb_debug("got unknown event(%d) event_len(%d) i=%d\n", len,event_len,i);
+ } else {
+ usb_debug("got event(%d) event_len(%d) i=%d\n", len,event_len,i);
+ return len; /* actually read length */
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ }
+ usb_debug("try get event again\n");
+ } while (retry < 3);
+ return -1;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_hci_suspend_cmd(struct LD_btmtk_usb_data *data)
+{
+ int ret = -1;
+#if SUPPORT_HISENSE_WoBLE
+ u8 cmd[] = {0xC9, 0xFC, 0x02, 0x01, 0x0D}; // for Hisense WoBLE
+
+ usb_debug("issue wake up command for Hisense\n");
+#else
+ u8 cmd[] = {0xC9, 0xFC, 0x0D, 0x01, 0x0E, 0x00, 0x05, 0x43,
+ 0x52, 0x4B, 0x54, 0x4D, 0x20, 0x04, 0x32, 0x00};
+
+ usb_debug("issue wake up command for '0E: MTK WoBLE Ver2'\n");
+#endif
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), NULL, -1);
+ if (ret < 0) {
+ usb_debug("error(%d)\n", ret);
+ return ret;
+ }
+ usb_debug("send suspend cmd OK\n");
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_hci_reset_cmd(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x03, 0x0C, 0x00 };
+ u8 event[] = { 0x0E, 0x04, 0x01, 0x03, 0x0C, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_hci_set_ce_cmd(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0xD1, 0xFC, 0x04, 0x0C, 0x07, 0x41, 0x00 };
+ u8 event[] = { 0x0E, 0x08, 0x01, 0xD1, 0xFC, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+
+ } else if (ret == sizeof(event) + 4) {
+ if (data->io_buf[6] & 0x01) {
+ usb_debug("warning, 0x41070c[0] is 1!\n");
+ ret = 0;
+ } else {
+ u8 cmd2[11] = { 0xD0, 0xFC, 0x08, 0x0C, 0x07, 0x41, 0x00 };
+
+ cmd2[7] = data->io_buf[6] | 0x01;
+ cmd2[8] = data->io_buf[7];
+ cmd2[9] = data->io_buf[8];
+ cmd2[10] = data->io_buf[9];
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd2, sizeof(cmd2), NULL, 0);
+ if (ret < 0) {
+ usb_debug("write 0x41070C failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ ret = 0;
+ }
+ }
+ } else {
+ usb_debug("failed, incorrect response length(%d)\n", ret);
+ return -1;
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_check_rom_patch_result_cmd(struct LD_btmtk_usb_data *data)
+{
+ /* Send HCI Reset */
+ {
+ int ret = 0;
+ unsigned char buf[8] = { 0 };
+ buf[0] = 0xD1;
+ buf[1] = 0xFC;
+ buf[2] = 0x04;
+ buf[3] = 0x00;
+ buf[4] = 0xE2;
+ buf[5] = 0x40;
+ buf[6] = 0x00;
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP,0x0, DEVICE_CLASS_REQUEST_OUT,
+ 0x00, 0x00, buf, 0x07, 100);
+ if (ret < 0)
+ {
+ usb_debug("error1(%d)\n", ret);
+ return ret;
+ }
+ }
+ /* Get response of HCI reset */
+ {
+ int ret = 0;
+ unsigned char buf[LD_BT_MAX_EVENT_SIZE] = { 0 };
+ int actual_length = 0;
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, buf, LD_BT_MAX_EVENT_SIZE,
+ &actual_length, 2000);
+ if (ret < 0)
+ {
+ usb_debug("error2(%d)\n", ret);
+ return ret;
+ }
+ usb_debug("Check rom patch result : ");
+
+ if (buf[6] == 0 && buf[7] == 0 && buf[8] == 0 && buf[9] == 0)
+ {
+ usb_debug("NG\n");
+ }
+ else
+ {
+ usb_debug("OK\n");
+ }
+ }
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_switch_iobase(struct LD_btmtk_usb_data *data, int base)
+{
+ int ret = 0;
+
+ switch (base)
+ {
+ case SYSCTL:
+ data->w_request = 0x42;
+ data->r_request = 0x47;
+ break;
+ case WLAN:
+ data->w_request = 0x02;
+ data->r_request = 0x07;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static void btmtk_usb_cap_init(struct LD_btmtk_usb_data *data)
+{
+ unsigned char *str_end ;
+ btmtk_usb_io_read32(data, 0x00, &data->chip_id);
+ if (data->chip_id == 0)
+ btmtk_usb_io_read32_7xxx(data, 0x80000008, &data->chip_id);
+
+ usb_debug("chip id = %x\n", data->chip_id);
+
+ if (is_mt7662T(data) || is_mt7632T(data)) {
+ usb_debug("btmtk:This is 7662T chip\n");
+
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ os_memcpy(data->rom_patch_bin_file_name, "mt7662t_patch_e1_hdr.bin", 24);
+ data->rom_patch_offset = 0xBC000;
+ data->rom_patch_len = 0;
+
+ } else if (is_mt7632(data) || is_mt7662(data)) {
+ usb_debug("btmtk:This is 7662 chip\n");
+
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ os_memcpy(data->rom_patch_bin_file_name, "mt7662_patch_e3_hdr.bin", 23);
+ data->rom_patch_offset = 0x90000;
+ data->rom_patch_len = 0;
+
+ } else if (is_mt7668(data) || is_mt7663(data)){
+ unsigned int fw_ver = 0;
+
+ btmtk_usb_io_read32_7xxx(data, 0x80000004, &fw_ver);
+ usb_debug("fw_ver = %x\n", fw_ver);
+ if ((fw_ver & 0xFF) != 0xFF) {
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+
+ /* Bin filename format : "mt$$$$_patch_e%.bin"
+ * $$$$ : chip id
+ * % : fw version & 0xFF + 1 (in HEX)
+ */
+ //snprintf((char *)data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "mt%04x_patch_e%x_hdr.bin",data->chip_id & 0xFFFF, (fw_ver & 0xFF) + 1);
+ sprintf((char *)data->rom_patch_bin_file_name, "mt%04x_patch_e%x_hdr.bin",
+ data->chip_id & 0xFFFF, (fw_ver & 0xFF) + 1);
+
+ if(My_strlen((char *)data->rom_patch_bin_file_name) > MAX_BIN_FILE_NAME_LEN){
+ str_end = data->rom_patch_bin_file_name + MAX_BIN_FILE_NAME_LEN;
+ *str_end='\0';
+ }
+ usb_debug("patch name: %s", data->rom_patch_bin_file_name);
+ data->rom_patch_len = 0;
+ } else {
+ usb_debug("Incorrect firmware version: 0xFF");
+ return;
+ }
+
+ if (is_mt7668(data)) {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME_7668,
+ sizeof(WOBLE_SETTING_FILE_NAME_7668));
+ usb_debug("woble setting file name is %s", WOBLE_SETTING_FILE_NAME_7668);
+ } else if (is_mt7663(data)) {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME_7663,
+ sizeof(WOBLE_SETTING_FILE_NAME_7663));
+ usb_debug("woble setting file name is %s", WOBLE_SETTING_FILE_NAME_7663);
+ } else {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME,
+ sizeof(WOBLE_SETTING_FILE_NAME));
+ usb_debug("woble setting file name is %s", WOBLE_SETTING_FILE_NAME);
+ }
+ } else {
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_CHIP_ID, &data->chip_id);
+ if (is_mt7961(data) || is_mt7902(data)) {
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_FLAVOR, &data->flavor);
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_FW_VERSION, &data->fw_version);
+ } else {
+ usb_debug("Unknown Mediatek device(%04X)\n", data->chip_id);
+ return;
+ }
+
+ usb_debug("Chip ID = 0x%x\n", data->chip_id);
+ usb_debug("flavor = 0x%x\n", data->flavor);
+ usb_debug("FW Ver = 0x%x\n", data->fw_version);
+
+ memset(data->rom_patch_bin_file_name, 0, MAX_BIN_FILE_NAME_LEN);
+ if ((data->fw_version & 0xff) == 0xff) {
+ usb_debug("Wrong FW version : 0x%x !", data->fw_version);
+ return;
+ }
+
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ /* Bin filename format : "BT_RAM_CODE_MT%04x_%x_%x_hdr.bin"
+ * $$$$ : chip id
+ * % : fw version & 0xFF + 1 (in HEX)
+ */
+ if (is_mt7902(data->chip_id)) {
+ /* 7902 cant't use the same rule to recognize */
+ data->flavor = 0;
+ } else {
+ data->flavor = (data->flavor & 0x00000080) >> 7;
+ }
+ usb_debug("%s: flavor1 = 0x%x", __func__, data->flavor);
+ /* if flavor equals 1, it represent 7920, else it represent 7921 */
+ if (data->flavor) {
+ //snprintf(data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ // "BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", data->chip_id & 0xffff,
+ // (data->fw_version & 0xff) + 1);
+ sprintf((char *)data->rom_patch_bin_file_name,
+ "BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", data->chip_id & 0xffff,
+ (data->fw_version & 0xff) + 1);
+ } else {
+ //snprintf(data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ // "BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ // data->chip_id & 0xffff, (data->fw_version & 0xff) + 1);
+ sprintf(data->rom_patch_bin_file_name,
+ "BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ data->chip_id & 0xffff, (data->fw_version & 0xff) + 1);
+ }
+
+ if(My_strlen((char *)data->rom_patch_bin_file_name) > MAX_BIN_FILE_NAME_LEN){
+ str_end = data->rom_patch_bin_file_name + MAX_BIN_FILE_NAME_LEN;
+ *str_end='\0';
+ }
+ usb_debug("patch name: %s\n", data->rom_patch_bin_file_name);
+ data->rom_patch_len = 0;
+
+ if (is_mt7961(data) || is_mt7902(data)) {
+ (void)sprintf(data->woble_setting_file_name, "%s_%x.%s",
+ WOBLE_CFG_NAME_PREFIX, data->chip_id & 0xffff,
+ WOBLE_CFG_NAME_SUFFIX);
+ usb_debug("woble setting file name is %s", data->woble_setting_file_name);
+ } else {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME,
+ sizeof(WOBLE_SETTING_FILE_NAME));
+ usb_debug("woble setting file name is %s", WOBLE_SETTING_FILE_NAME);
+ }
+ }
+}
+
+#if CRC_CHECK
+//---------------------------------------------------------------------------
+static u16 checksume16(u8 *pData, int len)
+{
+ int sum = 0;
+
+ while (len > 1)
+ {
+ sum += *((u16 *) pData);
+
+ pData = pData + 2;
+
+ if (sum & 0x80000000)
+ {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+ len -= 2;
+ }
+
+ if (len)
+ sum += *((u8 *) pData);
+
+ while (sum >> 16)
+ {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+
+ return ~sum;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_chk_crc(struct LD_btmtk_usb_data *data, u32 checksum_len)
+{
+ int ret = 0;
+ mtkbt_dev_t *udev = data->udev;
+
+ usb_debug("\n");
+
+ os_memmove(data->io_buf, &data->rom_patch_offset, 4);
+ os_memmove(&data->io_buf[4], &checksum_len, 4);
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP,0x1, DEVICE_VENDOR_REQUEST_OUT,
+ 0x20, 0x00, data->io_buf, 8, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d)\n", ret);
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static u16 btmtk_usb_get_crc(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ mtkbt_dev_t *udev = data->udev;
+ u16 crc, count = 0;
+
+ usb_debug("\n");
+
+ while (1)
+ {
+ ret =
+ data->hcif->usb_control_msg(udev, MTKBT_CTRL_RX_EP, 0x01, DEVICE_VENDOR_REQUEST_IN,
+ 0x21, 0x00, data->io_buf, 2, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ crc = 0xFFFF;
+ usb_debug("error(%d)\n", ret);
+ }
+
+ os_memmove(&crc, data->io_buf, 2);
+
+ crc = le16_to_cpu(crc);
+
+ if (crc != 0xFFFF)
+ break;
+
+ MTK_MDELAY(100);
+
+ if (count++ > 100)
+ {
+ usb_debug("Query CRC over %d times\n", count);
+ break;
+ }
+ }
+
+ return crc;
+}
+#endif /* CRC_CHECK */
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_wmt_reset_cmd(struct LD_btmtk_usb_data *data)
+{
+ /* reset command */
+ u8 cmd[] = { 0x6F, 0xFC, 0x05, 0x01, 0x07, 0x01, 0x00, 0x04 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x07, 0x01, 0x00, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 20, 0);
+ if (ret < 0) {
+ usb_debug("Check reset wmt result : NG\n");
+ } else {
+ usb_debug("Check reset wmt result : OK\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_wmt_cfg(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ int index = 0;
+
+ usb_debug("send wmt cmd!\n");
+
+ for (index = 0; index < WMT_CMD_COUNT; index++) {
+ if (data->bt_cfg.wmt_cmd[index].content && data->bt_cfg.wmt_cmd[index].length) {
+ ret = btmtk_usb_send_wmt_cmd(data, data->bt_cfg.wmt_cmd[index].content,
+ data->bt_cfg.wmt_cmd[index].length, NULL, 0, 100, 10);
+ if (ret < 0) {
+ usb_debug("Send wmt cmd failed(%d)! Index: %d\n", ret, index);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static u16 btmtk_usb_get_rom_patch_result(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x01,
+ DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf, 7,
+ CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d)\n", ret);
+ }
+
+ if (data->io_buf[0] == 0xe4 &&
+ data->io_buf[1] == 0x05 &&
+ data->io_buf[2] == 0x02 &&
+ data->io_buf[3] == 0x01 &&
+ data->io_buf[4] == 0x01 &&
+ data->io_buf[5] == 0x00 &&
+ data->io_buf[6] == 0x00)
+ {
+ //usb_debug("Get rom patch result : OK\n");
+ }
+ else
+ {
+ usb_debug("Get rom patch result : NG\n");
+ }
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+#define SHOW_FW_DETAILS(s) \
+ usb_debug("%s = %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", s, \
+ tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3], \
+ tmp_str[4], tmp_str[5], tmp_str[6], tmp_str[7], \
+ tmp_str[8], tmp_str[9], tmp_str[10], tmp_str[11], \
+ tmp_str[12], tmp_str[13], tmp_str[14], tmp_str[15])
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_load_rom_patch(struct LD_btmtk_usb_data *data)
+{
+ u32 loop = 0;
+ u32 value;
+ s32 sent_len;
+ int ret = 0;
+ u32 patch_len = 0;
+ u32 cur_len = 0;
+ int real_len = 0;
+ int first_block = 1;
+ unsigned char phase;
+ void *buf;
+ char *pos;
+ unsigned char *tmp_str;
+
+ //usb_debug("begin\n");
+load_patch_protect:
+ btmtk_usb_switch_iobase(data, WLAN);
+ btmtk_usb_io_read32(data, SEMAPHORE_03, &value);
+ loop++;
+
+ if ((value & 0x01) == 0x00)
+ {
+ if (loop < 1000)
+ {
+ MTK_MDELAY(1);
+ goto load_patch_protect;
+ }
+ else
+ {
+ usb_debug("btmtk_usb_load_rom_patch ERR! Can't get semaphore! Continue\n");
+ }
+ }
+
+ btmtk_usb_switch_iobase(data, SYSCTL);
+
+ btmtk_usb_io_write32(data, 0x1c, 0x30);
+
+ btmtk_usb_switch_iobase(data, WLAN);
+
+ /* check ROM patch if upgrade */
+ if ((MT_REV_GTE(data, mt7662, REV_MT76x2E3)) || (MT_REV_GTE(data, mt7632, REV_MT76x2E3)))
+ {
+ btmtk_usb_io_read32(data, CLOCK_CTL, &value);
+ if ((value & 0x01) == 0x01)
+ {
+ usb_debug("btmtk_usb_load_rom_patch : no need to load rom patch\n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ goto error;
+ }
+ }
+ else
+ {
+ btmtk_usb_io_read32(data, COM_REG0, &value);
+ if ((value & 0x02) == 0x02)
+ {
+ usb_debug("btmtk_usb_load_rom_patch : no need to load rom patch\n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ goto error;
+ }
+ }
+
+ buf = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!buf)
+ {
+ ret = -ENOMEM;
+ goto error;
+ }
+ pos = buf;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name, NULL,
+ data->udev, &data->rom_patch_len);
+
+ if (!data->rom_patch)
+ {
+ usb_debug("please assign a rom patch(/vendor/firmware/%s)or(/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name,
+ data->rom_patch_bin_file_name);
+ ret = -1;
+ goto error;
+ }
+
+ tmp_str = data->rom_patch;
+ SHOW_FW_DETAILS("FW Version");
+ SHOW_FW_DETAILS("build Time");
+
+ tmp_str = data->rom_patch + 16;
+ usb_debug("platform = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 20;
+ usb_debug("HW/SW version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 24;
+ usb_debug("Patch version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ usb_debug("\nloading rom patch...\n");
+
+ cur_len = 0x00;
+ patch_len = data->rom_patch_len - PATCH_INFO_SIZE;
+
+ /* loading rom patch */
+ while (1)
+ {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE;
+ real_len = 0;
+ sent_len =
+ (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+
+ //usb_debug("patch_len = %d\n", patch_len);
+ //usb_debug("cur_len = %d\n", cur_len);
+ //usb_debug("sent_len = %d\n", sent_len);
+
+ if (sent_len > 0)
+ {
+ if (first_block == 1)
+ {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ }
+ else if (sent_len == sent_len_max)
+ {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ }
+ else
+ {
+ phase = PATCH_PHASE3;
+ }
+
+ /* prepare HCI header */
+ pos[0] = 0x6F;
+ pos[1] = 0xFC;
+ pos[2] = (sent_len + 5) & 0xFF;
+ pos[3] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ pos[4] = 0x01;
+ pos[5] = 0x01;
+ pos[6] = (sent_len + 1) & 0xFF;
+ pos[7] = ((sent_len + 1) >> 8) & 0xFF;
+
+ pos[8] = phase;
+
+ os_memcpy(&pos[9], data->rom_patch + PATCH_INFO_SIZE + cur_len, sent_len);
+
+ //usb_debug("sent_len + PATCH_HEADER_SIZE = %d, phase = %d\n",
+ //sent_len + PATCH_HEADER_SIZE, phase);
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, buf, sent_len + PATCH_HEADER_SIZE, &real_len, 0);
+
+ if (ret)
+ {
+ usb_debug("upload rom_patch err: %d\n", ret);
+ goto error;
+ }
+
+ MTK_MDELAY(1);
+
+ cur_len += sent_len;
+
+ }
+ else
+ {
+ usb_debug("loading rom patch... Done\n");
+ break;
+ }
+ }
+
+ MTK_MDELAY(20);
+ ret = btmtk_usb_get_rom_patch_result(data);
+ MTK_MDELAY(20);
+
+ /* Send Checksum request */
+ #if CRC_CHECK
+ int total_checksum = checksume16(data->rom_patch + PATCH_INFO_SIZE, patch_len);
+ btmtk_usb_chk_crc(data, patch_len);
+ MTK_MDELAY(20);
+ if (total_checksum != btmtk_usb_get_crc(data))
+ {
+ usb_debug("checksum fail!, local(0x%x) <> fw(0x%x)\n", total_checksum,
+ btmtk_usb_get_crc(data));
+ ret = -1;
+ goto error;
+ }
+ else
+ {
+ usb_debug("crc match!\n");
+ }
+ #endif
+ MTK_MDELAY(20);
+ /* send check rom patch result request */
+ btmtk_usb_send_check_rom_patch_result_cmd(data);
+ MTK_MDELAY(20);
+ /* CHIP_RESET */
+ ret = btmtk_usb_send_wmt_reset_cmd(data);
+ MTK_MDELAY(20);
+ /* BT_RESET */
+ btmtk_usb_send_hci_reset_cmd(data);
+ /* for WoBLE/WoW low power */
+ btmtk_usb_send_hci_set_ce_cmd(data);
+
+ error:
+ btmtk_usb_io_write32(data, SEMAPHORE_03, 0x1);
+ //usb_debug("end\n");
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_wmt_power_on_cmd_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 count = 0; /* retry 3 times */
+ u8 cmd[] = { 0x6F, 0xFC, 0x06, 0x01, 0x06, 0x02, 0x00, 0x00, 0x01 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x06, 0x01, 0x00 }; /* event[6] is key */
+ int ret = -1; /* if successful, 0 */
+
+ do {
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 100, 10);
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else if (ret == sizeof(event) + 1) {
+ switch (data->io_buf[6]) {
+ case 0: /* successful */
+ usb_debug("OK\n");
+ ret = 0;
+ break;
+ case 2: /* retry */
+ usb_debug("Try again\n");
+ continue;
+ default:
+ usb_debug("Unknown result: %02X\n", data->io_buf[6]);
+ return -1;
+ }
+ } else {
+ usb_debug("failed, incorrect response length(%d)\n", ret);
+ return -1;
+ }
+ } while (++count < 3 && ret > 0);
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_hci_tci_set_sleep_cmd_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x7A, 0xFC, 0x07, 0x05, 0x40, 0x06, 0x40, 0x06, 0x00, 0x00 };
+ u8 event[] = { 0x0E, 0x04, 0x01, 0x7A, 0xFC, 0x00 };
+ int ret = -1; /* if successful, 0 */
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_get_vendor_cap(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x53, 0xFD, 0x00 };
+ u8 event[6] = { 0x0E, 0x12, 0x01, 0x53, 0xFD, 0x00, /* ... */ };
+ int ret = -1;
+
+ // TODO: should not compare whole event
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ usb_debug("ret(%d) data= 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+ ret,data->io_buf[0],data->io_buf[1],data->io_buf[2],data->io_buf[16],
+ data->io_buf[17],data->io_buf[18],data->io_buf[19]);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_apcf_reserved_79xx(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ u8 reserve_apcf_cmd[] = { 0xC9, 0xFC, 0x05, 0x01, 0x30, 0x02, 0x61, 0x02 };
+ u8 reserve_apcf_event[] = { 0xE6, 0x02, 0x08, 0x11 };
+
+ ret = btmtk_usb_send_hci_cmd(data, reserve_apcf_cmd, sizeof(reserve_apcf_cmd),
+ reserve_apcf_event, sizeof(reserve_apcf_event));
+ if (ret < 0 ) {
+ usb_debug("Failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_send_read_bdaddr(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x09, 0x10, 0x00 };
+ u8 event[] = { 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00, /* 6 bytes are BDADDR */ };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0 || ret != 12 /*Event actual length */) {
+ usb_debug("Failed(%d)\n", ret);
+ return ret;
+ }
+ os_memcpy(data->local_addr, data->io_buf + 6, BD_ADDR_LEN);
+ usb_debug("ADDR: %02X-%02X-%02X-%02X-%02X-%02X\n",
+ data->local_addr[5], data->local_addr[4], data->local_addr[3],
+ data->local_addr[2], data->local_addr[1], data->local_addr[0]);
+ ret = 0;
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_set_apcf(struct LD_btmtk_usb_data *data, BOOL bin_file)
+{
+ int i = 0, ret = -1;
+ // Legacy RC pattern
+ u8 manufacture_data[] = { 0x57, 0xFD, 0x27, 0x06, 0x00, FIDX,
+ 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x43, 0x52, 0x4B, 0x54, 0x4D, /* manufacturer data */
+ 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* mask */
+ u8 filter_cmd[] = { 0x57, 0xFD, 0x0A, 0x01, 0x00, FIDX, 0x20, 0x00,
+ 0x00, 0x00, 0x01, 0x80, 0x00 };
+ u8 event[] = { 0x0E, 0x07, 0x01, 0x57, 0xFD, 0x00, /* ... */ };
+
+ if (is_mt7961(data) || is_mt7902(data)) {
+ manufacture_data[5] = BUZZARD_FIDX;
+ filter_cmd[5] = BUZZARD_FIDX;
+ }
+
+ if (bin_file) {
+ if (data->wake_dev_len) {
+ /* wake_on_ble.conf using 90(0x5A-FIDX) as filter_index */
+ u8 pos = 0;
+ u8 broadcast_addr[] = { 0x57, 0xFD, 0x0A, 0x02, 0x00, FIDX,
+ 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, // ADDRESS
+ 0x00 }; // 0: Public, 1: Random
+ u8 adv_pattern[] = { 0x57, 0xFD, 0x15, 0x06, 0x00, FIDX,
+ 0x71, 0x01, // VID
+ 0x04, 0x11, // PID
+ 0x00, 0x00, 0x00, 0x00, // IR key code
+ 0x00, // sequence number
+ 0xFF, 0xFF, // mask~
+ 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00};
+
+ // BDADDR
+ for (i = 0; i < data->wake_dev[1]; i++) {
+ broadcast_addr[11] = data->wake_dev[2 + i * BD_ADDR_LEN + 0];
+ broadcast_addr[10] = data->wake_dev[2 + i * BD_ADDR_LEN + 1];
+ broadcast_addr[9] = data->wake_dev[2 + i * BD_ADDR_LEN + 2];
+ broadcast_addr[8] = data->wake_dev[2 + i * BD_ADDR_LEN + 3];
+ broadcast_addr[7] = data->wake_dev[2 + i * BD_ADDR_LEN + 4];
+ broadcast_addr[6] = data->wake_dev[2 + i * BD_ADDR_LEN + 5];
+ ret = btmtk_usb_send_hci_cmd(data, broadcast_addr, sizeof(broadcast_addr),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set broadcast address fail\n");
+ continue;
+ }
+ // mask broadcast address as a filter condition
+ filter_cmd[6] = 0x21;
+ }
+ usb_debug("There are %d broadcast address filter(s) from %s\n", i, WAKE_DEV_RECORD);
+
+ /** VID/PID in conf is LITTLE endian, but PID in ADV is BIG endian */
+ pos = 2 + data->wake_dev[1] * 6;
+ for (i = 0; i < data->wake_dev[pos]; i++) {
+ adv_pattern[6] = data->wake_dev[pos + (i * 4) + 1];
+ adv_pattern[7] = data->wake_dev[pos + (i * 4) + 2];
+ adv_pattern[9] = data->wake_dev[pos + (i * 4) + 3];
+ adv_pattern[8] = data->wake_dev[pos + (i * 4) + 4];
+ ret = btmtk_usb_send_hci_cmd(data, adv_pattern, sizeof(adv_pattern),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set advertising patten fail\n");
+ return ret;
+ }
+ }
+ usb_debug("There are %d manufacture data filter(s) from %s\n", i, WAKE_DEV_RECORD);
+
+ // Filtering parameters
+ ret = btmtk_usb_send_hci_cmd(data, filter_cmd, sizeof(filter_cmd),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set filtering parm fail\n");
+ return ret;
+ }
+
+ // if wake_on_ble.conf exist, no need use default woble_setting.bin
+ } else {
+ // woble_setting.bin
+ usb_debug("Set APCF filter from woble_setting.bin\n");
+ for (i = 0; i < WOBLE_SETTING_COUNT; i++) {
+ if (!data->woble_setting_apcf[i].length)
+ continue;
+
+ if ((data->woble_setting_apcf_fill_mac[i].content[0] == 1) &&
+ data->woble_setting_apcf_fill_mac_location[i].length) {
+ /* need add BD addr to apcf cmd */
+ memcpy(data->woble_setting_apcf[i].content +
+ (*data->woble_setting_apcf_fill_mac_location[i].content),
+ data->local_addr, BD_ADDR_LEN);
+ }
+
+ usb_debug_raw(data->woble_setting_apcf[i].content,
+ data->woble_setting_apcf[i].length,
+ "Send woble_setting_apcf[%d] ", i);
+
+ ret = btmtk_usb_send_hci_cmd(data, data->woble_setting_apcf[i].content,
+ data->woble_setting_apcf[i].length, event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set apcf_cmd[%d] data fail\n", i);
+ return ret;
+ }
+ }
+ }
+ } else {
+ // Use default
+ usb_debug("Using default APCF filter\n");
+ os_memcpy(manufacture_data + 9, data->local_addr, BD_ADDR_LEN);
+ ret = btmtk_usb_send_hci_cmd(data, manufacture_data,
+ sizeof(manufacture_data), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set manufacture data fail\n");
+ return ret;
+ }
+
+ ret = btmtk_usb_send_hci_cmd(data, filter_cmd, sizeof(filter_cmd),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set manufacture data fail\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_check_need_load_patch_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x6F, 0xFC, 0x05, 0x01, 0x17, 0x01, 0x00, 0x01 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x17, 0x01, 0x00, /* 0x02 */ }; /* event[6] is key */
+ int ret = -1;
+
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 20, 0);
+ /* can't get correct event */
+ if (ret < 0) {
+ usb_debug("check need load patch or not fail(%d)\n", ret);
+ return PATCH_ERR;
+ }
+
+ if (ret >= sizeof(event) + 1) {
+ usb_debug("%s: return len is %d\n", __func__, ret);
+ return data->io_buf[6];
+ }
+
+ return PATCH_ERR;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_load_partial_rom_patch_7668(struct LD_btmtk_usb_data *data,
+ u32 patch_len, int offset)
+{
+ u8 *pos = NULL;
+ u8 phase = 0;
+ s32 sent_len = 0;
+ u32 cur_len = 0;
+ int real_len = 0;
+ int first_block = 1;
+ int ret = 0;
+ void *buf = NULL;
+
+ buf = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!buf) {
+ return -ENOMEM;
+ }
+ pos = buf;
+
+ /* loading rom patch */
+ while (1) {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE;
+
+ real_len = 0;
+ sent_len = (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+ if (sent_len > 0) {
+ if (first_block == 1) {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ } else if (sent_len == sent_len_max) {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ } else {
+ phase = PATCH_PHASE3;
+ }
+
+ /* prepare HCI header */
+ pos[0] = 0x6F;
+ pos[1] = 0xFC;
+ pos[2] = (sent_len + 5) & 0xFF;
+ pos[3] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ pos[4] = 0x01;
+ pos[5] = 0x01;
+ pos[6] = (sent_len + 1) & 0xFF;
+ pos[7] = ((sent_len + 1) >> 8) & 0xFF;
+
+ pos[8] = phase;
+
+ os_memcpy(&pos[9], data->rom_patch + offset + cur_len, sent_len);
+ //usb_debug("sent_len = %d, cur_len = %d, phase = %d\n", sent_len, cur_len, phase);
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, buf,
+ sent_len + PATCH_HEADER_SIZE, &real_len, 0);
+ if (ret) {
+ usb_debug("upload rom_patch err: %d\n", ret);
+ ret = -1;
+ goto free;
+ }
+ cur_len += sent_len;
+ MTK_MDELAY(1);
+ btmtk_usb_get_rom_patch_result(data);
+ MTK_MDELAY(1);
+
+ } else {
+ usb_debug("loading rom patch... Done\n");
+ break;
+ }
+ os_memset(buf, 0, UPLOAD_PATCH_UNIT);
+ }
+free:
+ os_kfree(buf);
+ buf = NULL;
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+static int btmtk_usb_load_rom_patch_7668(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ int patch_status = 0;
+ int retry = 20;
+ unsigned char *tmp_str = NULL;
+ BOOL sysram3 = FALSE;
+ u32 patch_len = 0;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name,
+ NULL, data->udev, &data->rom_patch_len);
+ if (!data->rom_patch || !data->rom_patch_len) {
+ usb_debug("please assign a rom patch from (/etc/firmware/%s) or (/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name, data->rom_patch_bin_file_name);
+ return -1;
+ }
+
+ if (is_mt7668(data))
+ sysram3 = data->rom_patch_len > (PATCH_INFO_SIZE + PATCH_LEN_ILM) ? TRUE : FALSE;
+
+ do {
+ patch_status = btmtk_usb_check_need_load_patch_7668(data);
+ usb_debug("patch_status: %d, retry: %d\n", patch_status, retry);
+ if (patch_status > PATCH_NEED_DOWNLOAD || patch_status == PATCH_ERR) {
+ usb_debug("%s: patch_status error\n", __func__);
+ return -1;
+ } else if (patch_status == PATCH_READY) {
+ if (sysram3 == TRUE) {
+ usb_debug("%s: Prepare to load sysram3\n", __func__);
+ goto sysram;
+ }
+ usb_debug("%s: No need to load ROM patch\n", __func__);
+ return 0;
+ } else if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ MTK_MDELAY(100);
+ retry--;
+ } else if (patch_status == PATCH_NEED_DOWNLOAD) {
+ if (is_mt7663(data)) {
+ ret = btmtk_usb_send_wmt_cfg(data);
+ if (ret < 0) {
+ usb_debug("send wmt cmd failed(%d)\n", ret);
+ return ret;
+ }
+ }
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ usb_debug("Hold by another fun more than 2 seconds");
+ return -1;
+ }
+
+ tmp_str = data->rom_patch;
+ SHOW_FW_DETAILS("FW Version");
+ SHOW_FW_DETAILS("build Time");
+
+ tmp_str = data->rom_patch + 16;
+ usb_debug("platform = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 20;
+ usb_debug("HW/SW version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 24;
+
+ usb_debug("%s: loading rom patch of ILM\n", __func__);
+ patch_len = sysram3 ? PATCH_LEN_ILM : (data->rom_patch_len - PATCH_INFO_SIZE);
+ ret = btmtk_usb_load_partial_rom_patch_7668(data, patch_len, PATCH_INFO_SIZE);
+ if (ret < 0)
+ return ret;
+
+ /* CHIP_RESET, ROM patch would be reactivated.
+ * Currently, wmt reset is only for ILM rom patch, and there are also
+ * some preparations need to be done in FW for loading sysram3 patch...
+ */
+ MTK_MDELAY(20);
+ ret = btmtk_usb_send_wmt_reset_cmd(data);
+ if (ret < 0)
+ return ret;
+ MTK_MDELAY(20);
+
+sysram:
+ if (sysram3) {
+ usb_debug("%s: loading rom patch of sysram3\n", __func__);
+ patch_len = data->rom_patch_len - PATCH_INFO_SIZE - PATCH_LEN_ILM - PATCH_INFO_SIZE;
+ ret = btmtk_usb_load_partial_rom_patch_7668(data, patch_len,
+ PATCH_INFO_SIZE + PATCH_LEN_ILM + PATCH_INFO_SIZE);
+ }
+ return ret;
+}
+
+static void btmtk_print_bt_patch_info(struct LD_btmtk_usb_data *data)
+{
+ struct _PATCH_HEADER *patchHdr = NULL;
+ struct _Global_Descr *globalDesrc = NULL;
+
+ if (data->rom_patch == NULL) {
+ usb_debug("data->rom_patch is NULL!\n");
+ return;
+ }
+
+ patchHdr = (struct _PATCH_HEADER *)data->rom_patch;
+
+ if (is_mt7961(data) || is_mt7902(data))
+ globalDesrc = (struct _Global_Descr *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE);
+
+ usb_debug("[btmtk] =============== Patch Info ==============\n");
+ if (patchHdr) {
+ usb_debug("[btmtk] Built Time = %s\n", patchHdr->ucDateTime);
+ usb_debug("[btmtk] Hw Ver = 0x%04x\n", patchHdr->u2HwVer);
+ usb_debug("[btmtk] Sw Ver = 0x%04x\n", patchHdr->u2SwVer);
+ usb_debug("[btmtk] Magic Number = 0x%08x\n", patchHdr->u4MagicNum);
+
+ usb_debug("[btmtk] Platform = %c%c%c%c\n",
+ patchHdr->ucPlatform[0],
+ patchHdr->ucPlatform[1],
+ patchHdr->ucPlatform[2],
+ patchHdr->ucPlatform[3]);
+ } else
+ usb_debug("patchHdr is NULL!\n");
+
+ if (globalDesrc) {
+ usb_debug("[btmtk] Patch Ver = 0x%08x\n", globalDesrc->u4PatchVer);
+ usb_debug("[btmtk] Section num = 0x%08x\n", globalDesrc->u4SectionNum);
+ } else
+ usb_debug("globalDesrc is NULL!\n");
+ usb_debug("[btmtk] =========================================\n");
+}
+
+int btmtk_cif_send_control_out(struct LD_btmtk_usb_data *data,
+ const uint8_t *cmd, const int cmd_len, int delay, int retry)
+{
+ int ret = 0;
+
+ if (data == NULL || data->udev == NULL || data->io_buf == NULL || cmd == NULL) {
+ usb_debug("%s: incorrect cmd pointer\n", __func__);
+ ret = -1;
+ return ret;
+ }
+
+ usb_debug_raw(cmd, cmd_len, "%s: cmd:", __func__);
+ /* send WMT command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0x01,
+ DEVICE_CLASS_REQUEST_OUT, 0x30, 0x00, (void *)cmd, cmd_len,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int btmtk_cif_send_bulk_out(struct LD_btmtk_usb_data *data,
+ const uint8_t *cmd, const int cmd_len)
+{
+ int ret = 0;
+ int real_len = 0;
+
+ if (data == NULL || data->udev == NULL || cmd == NULL) {
+ usb_debug("%s: incorrect cmd pointer\n", __func__);
+ ret = -1;
+ return ret;
+ }
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, cmd, cmd_len, &real_len, 0);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int btmtk_cif_send_cmd(struct LD_btmtk_usb_data *data, const uint8_t *cmd,
+ const int cmd_len, int delay, int retry, int endpoint)
+{
+ int ret = -1;
+
+ if (endpoint == BTMTK_EP_TYPE_OUT_CMD) {
+ /* handle wmt cmd from driver */
+ ret = btmtk_cif_send_control_out(data, cmd, cmd_len,
+ delay, retry);
+ } else if (endpoint == BTMTK_EP_TPYE_OUT_ACL) {
+ /* bulk out for load rom patch*/
+ ret = btmtk_cif_send_bulk_out(data, cmd, cmd_len);
+ }
+
+ return ret;
+}
+
+int btmtk_cif_recv_evt(struct LD_btmtk_usb_data *data, int delay, int retry)
+{
+ int ret = -1; /* if successful, 0 */
+ unsigned int ifnum_base;
+
+ if (!data) {
+ usb_debug("%s: data == NULL!\n", __func__);
+ return ret;
+ }
+
+ if (!data->udev) {
+ usb_debug("%s: invalid parameters!\n", __func__);
+ return ret;
+ }
+
+get_response_again:
+ /* ms delay */
+ MTK_MDELAY(delay);
+
+ /* check WMT event */
+ memset(data->io_buf, 0, LD_BT_MAX_EVENT_SIZE);
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP,
+ 0x01, DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0) {
+ usb_debug("%s: event get failed(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ if (ret > 0) {
+ //usb_debug_raw(data->io_buf, ret, "%s OK: EVT:", __func__);
+ return ret; /* return read length */
+ } else if (retry > 0) {
+ if (retry == 1)
+ usb_debug("%s: Trying to get response... (%d)\n", __func__, ret);
+ retry--;
+ goto get_response_again;
+ } else
+ usb_debug("%s NG: do not got response:(%d)\n", __func__, ret);
+
+ return -1;
+}
+
+int btmtk_compare_evt(struct LD_btmtk_usb_data *data, const uint8_t *event,
+ int event_len, int recv_evt_len)
+{
+ int ret = -1;
+
+ if (data && data->io_buf && event && recv_evt_len >= event_len) {
+ if (memcmp(data->io_buf, event, event_len) == 0) {
+ ret = 0;
+ goto exit;
+ } else {
+ usb_debug("%s compare fail\n", __func__);
+ usb_debug_raw(event, event_len, "%s: event_need_compare:", __func__);
+ usb_debug_raw(data->io_buf, recv_evt_len, "%s: RCV:", __func__);
+ goto exit;
+ }
+ } else
+ usb_debug("%s invalid parameter!\n", __func__);
+
+exit:
+ return ret;
+}
+
+int btmtk_main_send_cmd(struct LD_btmtk_usb_data *data, const uint8_t *cmd,
+ const int cmd_len, const uint8_t *event, const int event_len, int delay,
+ int retry, int endpoint)
+{
+ int ret = 0;
+ unsigned long comp_event_timo = 0, start_time = 0;
+
+ if (data == NULL || cmd == NULL) {
+ usb_debug("%s, invalid parameters!\n", __func__);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = btmtk_cif_send_cmd(data, cmd, cmd_len, delay, retry, endpoint);
+ if (ret < 0) {
+ usb_debug("%s btmtk_cif_send_cmd failed!!\n", __func__);
+ return ret;
+ }
+
+ /* wmt cmd and download fw patch using wmt cmd with USB interface, need use
+ * usb_control_msg to recv wmt event;
+ */
+ if (event && (endpoint == BTMTK_EP_TYPE_OUT_CMD || endpoint == BTMTK_EP_TPYE_OUT_ACL)) {
+ data->recv_evt_len = btmtk_cif_recv_evt(data, delay, retry);
+ if (data->recv_evt_len < 0) {
+ usb_debug("%s btmtk_cif_recv_evt failed!!\n", __func__);
+ ret = -1;
+ return ret;
+ }
+ ret = btmtk_compare_evt(data, event, event_len, data->recv_evt_len);
+ }
+
+ return ret;
+}
+
+static int btmtk_send_wmt_download_cmd(struct LD_btmtk_usb_data *data,
+ u8 *cmd, int cmd_len, u8 *event, int event_len, struct _Section_Map *sectionMap,
+ u8 fw_state, u8 dma_flag)
+{
+ int payload_len = 0;
+ int ret = -1;
+ int i = 0;
+ u32 revert_SecSpec = 0;
+
+ if (data == NULL || cmd == NULL || event == NULL || sectionMap == NULL) {
+ usb_debug("%s: invalid parameter!", __func__);
+ return ret;
+ }
+
+ /* need refine this cmd to mtk_wmt_hdr struct*/
+ /* prepare HCI header */
+ cmd[0] = 0x6F;
+ cmd[1] = 0xFC;
+
+ /* prepare WMT header */
+ cmd[3] = 0x01;
+ cmd[4] = 0x01; /* opcode */
+
+ if (fw_state == 0) {
+ /* prepare WMT DL cmd */
+ payload_len = SEC_MAP_NEED_SEND_SIZE + 2;
+
+ cmd[2] = (payload_len + 4) & 0xFF; /* length*/
+ cmd[5] = payload_len & 0xFF;
+ cmd[6] = (payload_len >> 8) & 0xFF;
+ cmd[7] = 0x00; /* which is the FW download state 0 */
+ cmd[8] = dma_flag; /* 1:using DMA to download, 0:using legacy wmt cmd*/
+ cmd_len = SEC_MAP_NEED_SEND_SIZE + PATCH_HEADER_SIZE;
+
+ memcpy(&cmd[PATCH_HEADER_SIZE], (u8 *)(sectionMap->u4SecSpec), SEC_MAP_NEED_SEND_SIZE);
+
+ usb_debug_raw(cmd, cmd_len, "%s: CMD:", __func__);
+
+ ret = btmtk_main_send_cmd(data, cmd, cmd_len,
+ event, event_len, 20, 0, BTMTK_EP_TYPE_OUT_CMD);
+ if (ret < 0) {
+ usb_debug("%s: send wmd dl cmd failed, terminate!\n", __func__);
+ return PATCH_ERR;
+ }
+
+ if (data->recv_evt_len >= event_len)
+ return data->io_buf[PATCH_STATUS];
+
+ return PATCH_ERR;
+ } else
+ usb_debug("%s: fw state is error!\n", __func__);
+
+ return ret;
+}
+
+static int btmtk_load_fw_patch_using_wmt_cmd(struct LD_btmtk_usb_data *data,
+ u8 *image, u8 *event, int event_len, u32 patch_len, int offset)
+{
+ int ret = 0;
+ u32 cur_len = 0;
+ s32 sent_len;
+ int first_block = 1;
+ u8 phase;
+ int delay = PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME;
+ int retry = PATCH_DOWNLOAD_PHASE1_2_RETRY;
+
+ if (data == NULL || image == NULL || data->rom_patch == NULL) {
+ usb_debug("%s, invalid parameters!\n", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ /* loading rom patch */
+ while (1) {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE;
+
+ sent_len = (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+
+ if (sent_len > 0) {
+ if (first_block == 1) {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ } else if (sent_len == sent_len_max) {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ } else {
+ phase = PATCH_PHASE3;
+ }
+
+
+ /* prepare HCI header */
+ image[0] = 0x6F;
+ image[1] = 0xFC;
+ image[2] = (sent_len + 5) & 0xFF;
+ image[3] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ image[4] = 0x01;
+ image[5] = 0x01;
+ image[6] = (sent_len + 1) & 0xFF;
+ image[7] = ((sent_len + 1) >> 8) & 0xFF;
+
+ image[8] = phase;
+ memcpy(&image[PATCH_HEADER_SIZE], data->rom_patch + offset + cur_len, sent_len);
+ if (phase == PATCH_PHASE3) {
+ delay = PATCH_DOWNLOAD_PHASE3_DELAY_TIME;
+ retry = PATCH_DOWNLOAD_PHASE3_RETRY;
+ }
+
+ cur_len += sent_len;
+ //usb_debug("%s: sent_len = %d, cur_len = %d, phase = %d\n", __func__,
+ // sent_len, cur_len, phase);
+
+ ret = btmtk_main_send_cmd(data, image, sent_len + PATCH_HEADER_SIZE,
+ event, event_len, delay, retry, BTMTK_EP_TPYE_OUT_ACL);
+ if (ret < 0) {
+ usb_debug("%s: send patch failed, terminate\n", __func__);
+ goto exit;
+ }
+ } else
+ break;
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_send_fw_rom_patch_79xx(struct LD_btmtk_usb_data *data)
+{
+ u8 *pos;
+ HAL_TIME_T start_time;
+ HAL_TIME_T end_time;
+ int loop_count = 0;
+ int ret = 0;
+ u32 section_num = 0;
+ u32 section_offset = 0;
+ u32 dl_size = 0;
+ int patch_status = 0;
+ int retry = 20;
+ u8 dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ struct _Section_Map *sectionMap;
+ struct _Global_Descr *globalDescr;
+ u8 event[] = {0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[6] is status*/
+
+ if (data->rom_patch == NULL) {
+ usb_debug("data->rom_patch is NULL!");
+ ret = -1;
+ goto exit;
+ }
+
+ globalDescr = (struct _Global_Descr *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE);
+
+ usb_debug("%s: loading rom patch...\n", __func__);
+ HAL_GetTime(&start_time);
+
+ section_num = globalDescr->u4SectionNum;
+ usb_debug("%s: section_num = 0x%08x\n", __func__, section_num);
+
+ pos = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!pos) {
+ usb_debug("%s: alloc memory failed\n", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ do {
+ sectionMap = (struct _Section_Map *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE +
+ FW_ROM_PATCH_GD_SIZE + FW_ROM_PATCH_SEC_MAP_SIZE * loop_count);
+
+ section_offset = sectionMap->u4SecOffset;
+ dl_size = sectionMap->bin_info_spec.u4DLSize;
+
+ usb_debug("%s: loop_count = %d, section_offset = 0x%08x, download patch_len = 0x%08x\n",
+ __func__, loop_count, section_offset, dl_size);
+
+ if (dl_size > 0) {
+ retry = 20;
+ do {
+ patch_status = btmtk_send_wmt_download_cmd(data, pos, 0,
+ event, sizeof(event) - 1, sectionMap, 0, dma_flag);
+ usb_debug("%s: patch_status %d\n", __func__, patch_status);
+
+ if (patch_status > BUZZARD_PATCH_READY || patch_status == PATCH_ERR) {
+ usb_debug("%s: patch_status error\n", __func__);
+ ret = -1;
+ goto err;
+ } else if (patch_status == BUZZARD_PATCH_READY) {
+ usb_debug("%s: no need to load rom patch section%d\n", __func__, loop_count);
+ goto next_section;
+ } else if (patch_status == BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER) {
+ MTK_MDELAY(100);
+ retry--;
+ } else if (patch_status == BUZZARD_PATCH_NEED_DOWNLOAD) {
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER) {
+ usb_debug("%s: Hold by another fun more than 2 seconds\n", __func__);
+ ret = -1;
+ goto err;
+ }
+
+ if (dma_flag == PATCH_DOWNLOAD_USING_DMA) {
+ /* using DMA to download fw patch*/
+ } else {
+ /* using legacy wmt cmd to download fw patch */
+ ret = btmtk_load_fw_patch_using_wmt_cmd(data, pos, event,
+ sizeof(event) - 1, dl_size, section_offset);
+ if (ret < 0) {
+ usb_debug("%s: btmtk_load_fw_patch_using_wmt_cmd failed!\n", __func__);
+ goto err;
+ }
+ }
+ }
+
+next_section:
+ continue;
+ } while (++loop_count < section_num);
+
+err:
+ os_kfree(pos);
+ pos = NULL;
+
+exit:
+ HAL_GetTime(&end_time);
+ usb_debug("%s: loading rom patch... Done, total use: %d us\n", __func__,
+ (1000000 * (end_time.u4Seconds - start_time.u4Seconds) +
+ end_time.u4Micros - start_time.u4Micros));
+
+ return ret;
+}
+
+int btmtk_load_rom_patch_79xx(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name,
+ NULL, data->udev, &data->rom_patch_len);
+ if (!data->rom_patch || !data->rom_patch_len) {
+ usb_debug("please assign a rom patch from (/etc/firmware/%s) or (/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name, data->rom_patch_bin_file_name);
+ return -1;
+ }
+
+ btmtk_print_bt_patch_info(data);
+
+ ret = btmtk_send_fw_rom_patch_79xx(data);
+ if (ret < 0) {
+ usb_debug("%s, btmtk_send_fw_rom_patch_79xx failed!\n", __func__);
+ return ret;
+ }
+
+ usb_debug("btmtk_load_rom_patch_79xx end\n");
+
+ return ret;
+}
+
+static int btmtk_usb_load_fw_cfg_setting(char *block_name, struct fw_cfg_struct *save_content,
+ int counter, u8 *searchcontent, enum fw_cfg_index_len index_length)
+{
+ int ret = 0, i = 0;
+ int temp_len = 0;
+ u8 temp[260]; /* save for total hex number */
+ char *search_result = NULL;
+ char *search_end = NULL;
+ char search[32];
+ char *next_block = NULL;
+#define CHAR2HEX_SIZE 4
+ char number[CHAR2HEX_SIZE + 1]; /* 1 is for '\0' */
+ char *temp_num = number;
+
+ x_memset(search, 0, sizeof(search));
+ x_memset(temp, 0, sizeof(temp));
+ x_memset(number, 0, sizeof(number));
+
+ /* search block name */
+ for (i = 0; i < counter; i++) {
+ temp_len = 0;
+ if (index_length == FW_CFG_INX_LEN_2) /* EX: APCF01 */
+ sprintf(search, "%s%02d:", block_name, i);
+ else if (index_length == FW_CFG_INX_LEN_3) /* EX: APCF001 */
+ sprintf(search, "%s%03d:", block_name, i);
+ else
+ sprintf(search, "%s:", block_name);
+
+ if(My_strlen(search) > 32){
+ usb_debug("block_name longer than 32 \n");
+ }
+ search_result = My_strstr((char *)searchcontent, search);
+ if (search_result) {
+ x_memset(temp, 0, sizeof(temp));
+ search_result = My_strstr(search_result, "0x");
+ /* find next line as end of this command line, if NULL means last line */
+ next_block = My_strstr(search_result, ":");
+
+ do {
+ search_end = My_strstr(search_result, ",");
+ if (search_end - search_result != CHAR2HEX_SIZE) {
+ usb_debug("Incorrect Format in %s\n", search);
+ break;
+ }
+
+ x_memset(number, 0, sizeof(number));
+ x_memcpy(number, search_result, CHAR2HEX_SIZE);
+
+ temp[temp_len++] = strtol(number, &temp_num, 0);
+ search_result = My_strstr(search_end, "0x");
+ } while (search_result < next_block || (search_result && next_block == NULL));
+ }
+
+ if (temp_len && temp_len < sizeof(temp)) {
+ usb_debug("%s found & stored in %d\n", search, i);
+ save_content[i].content = os_kzalloc(temp_len, MTK_GFP_ATOMIC);
+ if (save_content[i].content == NULL) {
+ usb_debug("Allocate memory fail(%d)\n", i);
+ return -ENOMEM;
+ }
+ x_memcpy(save_content[i].content, temp, temp_len);
+ save_content[i].length = temp_len;
+ }
+ }
+
+ return ret;
+}
+
+//---------------------------------------------------------------------------
+int btmtk_usb_load_woble_setting(struct LD_btmtk_usb_data *data)
+{
+ int err = 0;
+ BOOL woble_setting_bin = FALSE;
+ BOOL wake_on_ble_conf = FALSE;
+
+ if (!data)
+ return -EINVAL;
+
+ /* For woble_setting.bin */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ }
+ data->setting_file_len = 0;
+
+ LD_load_code_from_bin(&data->setting_file, data->woble_setting_file_name,
+ NULL, data->udev, &data->setting_file_len);
+
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("Please make sure %s in the /vendor/firmware\n",
+ data->woble_setting_file_name);
+ woble_setting_bin = FALSE;
+ } else {
+ err = btmtk_usb_load_fw_cfg_setting("APCF", data->woble_setting_apcf,
+ WOBLE_SETTING_COUNT, data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("APCF_ADD_MAC",
+ data->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT,
+ data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("APCF_ADD_MAC_LOCATION",
+ data->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT,
+ data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("RADIOOFF", &data->woble_setting_radio_off,
+ 1, data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ switch (data->bt_cfg.unify_woble_type) {
+ case 0:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_LEGACY",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 1:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_WAVEFORM",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 2:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_IR",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ default:
+ usb_debug("unify_woble_type unknown(%d)\n", data->bt_cfg.unify_woble_type);
+ }
+ if (err) {
+ usb_debug("Parse unify_woble_type(%d) failed\n", data->bt_cfg.unify_woble_type);
+ goto LOAD_ERR;
+ }
+
+ err = btmtk_usb_load_fw_cfg_setting("RADIOOFF_COMPLETE_EVENT",
+ &data->woble_setting_radio_off_comp_event, 1, data->setting_file,
+ FW_CFG_INX_LEN_2);
+
+LOAD_ERR:
+ if (data->setting_file) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ data->setting_file_len = 0;
+ }
+
+ if (err) {
+ woble_setting_bin = FALSE;
+ usb_debug("err(%d), woble_setting_bin(%d)\n", err, woble_setting_bin);
+ } else
+ woble_setting_bin = TRUE;
+ }
+
+ /* For wake_on_ble.conf */
+ data->wake_dev = NULL;
+ data->wake_dev_len = 0;
+
+ LD_load_code_from_bin(&data->wake_dev, WAKE_DEV_RECORD, WAKE_DEV_RECORD_PATH,
+ data->udev, &data->wake_dev_len);
+ if (data->wake_dev == NULL || data->wake_dev_len == 0) {
+ usb_debug("There is no DEVICE RECORD for wake-up\n");
+ wake_on_ble_conf = FALSE;
+ } else {
+ // content check
+ if (data->wake_dev[0] != data->wake_dev_len || data->wake_dev_len < 3) {
+ usb_debug("Incorrect total length on %s\n", WAKE_DEV_RECORD);
+ data->wake_dev_len = 0;
+ os_kfree(data->wake_dev);
+ data->wake_dev = NULL;
+ wake_on_ble_conf = FALSE;
+ } else {
+ wake_on_ble_conf = TRUE;
+ }
+ }
+
+ if (woble_setting_bin == FALSE && wake_on_ble_conf == FALSE)
+ return -ENOENT;
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+int btmtk_usb_set_unify_woble(struct LD_btmtk_usb_data *data)
+{
+ int ret = -1;
+ u8 *radio_off = NULL;
+ int length = 0;
+ // Filter Index: 0x5A
+ u8 cmd[] = { 0xC9, 0xFC, 0x14, 0x01, 0x20, 0x02, 0x00, 0x01, 0x02, 0x01,
+ 0x00, 0x05, 0x10, 0x01, 0x00, 0x40, 0x06, 0x02, 0x40, FIDX, 0x02,
+ 0x41, 0x0F};
+ u8 event[] = { 0xE6, 0x02, 0x08, 0x00 };
+
+ if (is_mt7961(data) || is_mt7902(data)) {
+ cmd[19] = BUZZARD_FIDX;
+ }
+ if (data->woble_setting_radio_off.length && is_support_unify_woble(data)) {
+ /* start to send radio off cmd from woble setting file */
+ length = data->woble_setting_radio_off.length +
+ data->woble_setting_wakeup_type.length;
+ radio_off = os_kzalloc(length, MTK_GFP_ATOMIC);
+ if (!radio_off) {
+ usb_debug("alloc memory fail (radio_off)\n");
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ memcpy(radio_off, data->woble_setting_radio_off.content,
+ data->woble_setting_radio_off.length);
+ if (data->woble_setting_wakeup_type.length) {
+ memcpy(radio_off + data->woble_setting_radio_off.length,
+ data->woble_setting_wakeup_type.content,
+ data->woble_setting_wakeup_type.length);
+ radio_off[2] += data->woble_setting_wakeup_type.length;
+ }
+
+ usb_debug_raw(radio_off, length, "Send radio off");
+ ret = btmtk_usb_send_hci_cmd(data, radio_off,
+ length,
+ data->woble_setting_radio_off_comp_event.content,
+ data->woble_setting_radio_off_comp_event.length);
+ if (ret < 0) {
+ usb_debug("btmtk_usb_send_hci_cmd return fail %d\n", ret);
+ return ret;
+ }
+ }else { /* use default */
+ usb_debug("use default radio off cmd\n");
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0)
+ usb_debug("Failed(%d)\n", ret);
+ }
+ return ret;
+}
+
+static void btmtk_usb_free_fw_cfg_struct(struct fw_cfg_struct *fw_cfg, int count)
+{
+ int i = 0;
+
+ for (i = 0; i < count; i++) {
+ if (fw_cfg[i].content) {
+ os_kfree(fw_cfg[i].content);
+ fw_cfg[i].content = NULL;
+ fw_cfg[i].length = 0;
+ } else
+ fw_cfg[i].length = 0;
+ }
+ return;
+}
+static void btmtk_usb_free_setting_file(struct LD_btmtk_usb_data *data)
+{
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(&data->woble_setting_radio_off, 1);
+ btmtk_usb_free_fw_cfg_struct(&data->woble_setting_radio_off_comp_event, 1);
+ btmtk_usb_free_fw_cfg_struct(data->bt_cfg.wmt_cmd, WMT_CMD_COUNT);
+
+ x_memset(&data->bt_cfg, 0, sizeof(data->bt_cfg));
+
+ if (data->woble_setting_file_name) {
+ os_kfree(data->woble_setting_file_name);
+ data->woble_setting_file_name = NULL;
+ }
+ return;
+}
+
+static int btmtk_usb_parse_bt_cfg_file(char *item_name, char *text, u8 *searchcontent)
+{
+ int ret = 0;
+ char search[32];
+ char *ptr = NULL, *p = NULL;
+ char *temp = text;
+
+ if (text == NULL) {
+ usb_debug("text param is invalid!\n");
+ ret = -1;
+ goto out;
+ }
+
+ x_memset(search, 0, sizeof(search));
+
+ sprintf(search, "%s", item_name); /* EX: SUPPORT_UNIFY_WOBLE */
+
+ if(My_strlen(search) > 32){
+ usb_debug("item_name longer than 32 \n");
+ }
+
+ p = ptr = My_strstr((char *)searchcontent, search);
+ if (!ptr) {
+ usb_debug("Can't find %s\n", item_name);
+ ret = -1;
+ goto out;
+ }
+
+ if (p > (char *)searchcontent) {
+ p--;
+ while ((*p == ' ') && (p != (char *)searchcontent))
+ p--;
+ if (*p == '#') {
+ usb_debug("It's invalid bt cfg item\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ p = ptr + My_strlen(item_name) + 1;
+ ptr = p;
+
+ for (;;) {
+ switch (*p) {
+ case '\n':
+ goto textdone;
+ default:
+ *temp++ = *p++;
+ break;
+ }
+ }
+
+textdone:
+ *temp = '\0';
+
+out:
+ return ret;
+}
+
+static int btmtk_usb_load_bt_cfg_item(struct bt_cfg_struct *bt_cfg_content, u8 *searchcontent)
+{
+ int ret = 0;
+ char text[128]; /* save for search text */
+ char *temp_text = text;
+
+ x_memset(text, 0, sizeof(text));
+ ret = btmtk_usb_parse_bt_cfg_file(BT_UNIFY_WOBLE, text, searchcontent);
+ if (!ret) {
+ bt_cfg_content->support_unify_woble = strtol(text, &temp_text, 10);
+ usb_debug("bt_cfg_content->support_unify_woble = %d\n", bt_cfg_content->support_unify_woble);
+ } else {
+ usb_debug("search item %s is invalid!\n", BT_UNIFY_WOBLE);
+ }
+
+ ret = btmtk_usb_parse_bt_cfg_file(BT_UNIFY_WOBLE_TYPE, text, searchcontent);
+ if (!ret) {
+ bt_cfg_content->unify_woble_type = strtol(text, &temp_text, 10);
+ usb_debug("bt_cfg_content->unify_woble_type = %d\n", bt_cfg_content->unify_woble_type);
+ } else {
+ usb_debug("search item %s is invalid!\n", BT_UNIFY_WOBLE_TYPE);
+ }
+
+ ret = btmtk_usb_load_fw_cfg_setting(BT_WMT_CMD, bt_cfg_content->wmt_cmd,
+ WMT_CMD_COUNT, searchcontent, FW_CFG_INX_LEN_3);
+ if (ret)
+ usb_debug("%s: search item %s is invalid!", __func__, BT_WMT_CMD);
+ return ret;
+}
+
+int btmtk_usb_load_bt_cfg(char *cfg_name, struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ char bt_cfg_name[MAX_BIN_FILE_NAME_LEN] = {'\0'};
+
+ if (!data)
+ return -EINVAL;
+
+ /* For woble_setting.bin */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ }
+ data->setting_file_len = 0;
+
+ if ((data->chip_id & 0xFF00) == 0x7900) {
+ if (data->flavor && !is_mt7902(data))
+ (void)sprintf(bt_cfg_name, "%s%x_1a_%x.%s",
+ BT_CFG_NAME_PREFIX, data->chip_id & 0xffff,
+ (data->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ else
+ (void)sprintf(bt_cfg_name, "%s%x_1_%x.%s",
+ BT_CFG_NAME_PREFIX, data->chip_id & 0xffff,
+ (data->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ } else if ((data->chip_id & 0xFF00) == 0x7600)
+ (void)sprintf(bt_cfg_name, "%s%x.%s", BT_CFG_NAME_PREFIX_76XX,
+ data->chip_id & 0xffff, BT_CFG_NAME_SUFFIX);
+ else
+ (void)sprintf(bt_cfg_name, "bt.cfg");
+
+ LD_load_code_from_bin(&data->setting_file, bt_cfg_name, NULL, data->udev, &data->setting_file_len);
+
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("%s not exist in the /vendor/firmware\n", bt_cfg_name);
+
+ LD_load_code_from_bin(&data->setting_file, BT_CFG_NAME, NULL, data->udev, &data->setting_file_len);
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("%s not exist in the /vendor/firmware\n", BT_CFG_NAME);
+ usb_debug("Please make sure %s or %s in the /vendor/firmware\n", bt_cfg_name, BT_CFG_NAME);
+ return -ENOMEM;
+ }
+ }
+
+ ret = btmtk_usb_load_bt_cfg_item(&data->bt_cfg, data->setting_file);
+ if (ret) {
+ usb_debug("btmtk_usb_load_bt_cfg_item error return %d\n", ret);
+ return ret;
+ }
+
+ /* release setting file memory */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ data->setting_file_len = 0;
+ }
+ return ret;
+}
+
+static void btmtk_usb_initialize_cfg_items(struct LD_btmtk_usb_data *data)
+{
+ if (data == NULL) {
+ usb_debug("btmtk data NULL!\n");
+ return;
+ }
+
+ data->bt_cfg.support_unify_woble = 1;
+ data->bt_cfg.unify_woble_type = 0;
+ btmtk_usb_free_fw_cfg_struct(data->bt_cfg.wmt_cmd, WMT_CMD_COUNT);
+}
+
+//---------------------------------------------------------------------------int
+
+int Ldbtusb_getBtWakeT(struct LD_btmtk_usb_data *data){
+ //struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+ int ret = -1;
+ if (!data) {
+ usb_debug("btmtk data NULL!\n");
+ return ret;
+ }
+ if (is_mt7668(data)) {
+ usb_debug("is_mt7668(data \n");
+ //resume
+ u8 buf[] = {0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00};
+ u8 event[] = { 0xe6, 0x02, 0x08, 0x01 };
+ ret = btmtk_usb_send_hci_cmd(data, buf, sizeof(buf), event, sizeof(event));
+
+ if (ret < 0)
+ {
+ usb_debug("%s error1(%d)\n", __func__, ret);
+ return ret;
+ }
+ u8 cmd[] = { 0xCE, 0xFC, 0x04, 0x03, 0x00, 0x00, 0x00};
+ u8 event1[] = { 0xe8 };
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event1, sizeof(event1));
+ int len = 0;
+ u8 retry = 0;
+ u8 returnEvent = 0xe8;
+ usb_debug("\n");
+ do {
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, 100);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ }
+
+ if (returnEvent == data->io_buf[0]) {//get response from fw
+ usb_debug("mode %d\n",data->io_buf[37]);
+ if(data->io_buf[37] == 0x31) {//get soundmode bt wake type
+ //Xgimi_Set_MusicMode(TRUE);
+ }
+ break;
+ } else {
+ usb_debug("\n");
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ usb_debug("try get event again\n");
+ } while (retry < 5);
+ } else {
+ usb_debug("is_mt7662 data \n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ u8 buf[] = {0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00};
+ u8 event[] = { 0xe6, 0x02, 0x08, 0x01 };
+ ret = btmtk_usb_send_hci_cmd(data, buf, sizeof(buf), event, sizeof(event));
+
+ if (ret < 0) {
+ usb_debug("%s error1(%d)\n", __func__, ret);
+ return ret;
+ }
+ u8 cmd[] = { 0x4B, 0xFC, 0x04, 0x03, 0x00, 0x00, 0x00};
+ u8 event1[] = { 0x0f };
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event1, sizeof(event1));
+ int len = 0;
+ u8 retry = 0;
+ u8 returnEvent = 0xe8;
+
+ usb_debug("after send\n");
+ do {
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, 2000);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ }
+ if (returnEvent == data->io_buf[0]) {//get response from fw
+ usb_debug("mode %d\n",data->io_buf[37]);
+ if(data->io_buf[37] == 0x31){//get soundmode bt wake type
+ //Xgimi_Set_MusicMode(TRUE);
+ }
+ break;
+ } else {
+ usb_debug("\n");
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ usb_debug("try get event again\n");
+ } while (retry < 5);
+ }
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+u8 LD_btmtk_usb_getWoBTW(void)
+{
+ usb_debug("LD_btmtk_usb_getWoBTW, u8WoBTW = %d\n", u8WoBTW);
+ return u8WoBTW;
+}
+
+void LD_btmtk_usb_SetWoble(mtkbt_dev_t *dev)
+{
+ struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+ int ret = -1;
+ u8WoBTW = PM_SOURCE_DISABLE;
+
+ usb_debug("\n");
+ if (!data) {
+ usb_debug("btmtk data NULL!\n");
+ return;
+ }
+
+ if (!is_mt7662(data)) {
+ if (is_support_unify_woble(data)) {
+ if (data->bt_cfg.unify_woble_type == 1) {
+ /* BTW */
+ u8WoBTW = 1;
+ }
+
+ /* Power on sequence */
+ btmtk_usb_send_wmt_power_on_cmd_7668(data);
+ if ((data->chip_id & 0xFF00) == 0x7600) {
+ btmtk_usb_send_hci_tci_set_sleep_cmd_7668(data);
+ btmtk_usb_send_hci_reset_cmd(data);
+ btmtk_usb_get_vendor_cap(data);
+ }
+
+ /* Unify WoBLE flow */
+ if (is_mt7961(data) || is_mt7902(data))
+ btmtk_usb_send_apcf_reserved_79xx(data);
+
+ btmtk_usb_send_read_bdaddr(data);
+ ret = btmtk_usb_load_woble_setting(data);
+ if (ret) {
+ usb_debug("Using lagecy WoBLE setting(%d)!!!\n", ret);
+ btmtk_usb_set_apcf(data, FALSE);
+ } else
+ btmtk_usb_set_apcf(data, TRUE);
+ btmtk_usb_set_unify_woble(data);
+ }
+ } else {
+ btmtk_usb_send_hci_suspend_cmd(data);
+ }
+
+ // Clean & free buffer
+ btmtk_usb_free_setting_file(data);
+
+ if (data->wake_dev) {
+ os_kfree(data->wake_dev);
+ data->wake_dev = NULL;
+ data->wake_dev_len = 0;
+ }
+
+ return;
+}
+
+//---------------------------------------------------------------------------
+int LD_btmtk_usb_probe(mtkbt_dev_t *dev,int flag)
+{
+ struct LD_btmtk_usb_data *data;
+ int ret=0, err = 0;
+
+ usb_debug("=========================================\n");
+ usb_debug("Mediatek Bluetooth USB driver ver %s\n", LD_VERSION);
+ usb_debug("=========================================\n");
+ os_memcpy(driver_version, LD_VERSION, sizeof(LD_VERSION));
+ probe_counter++;
+ isbtready = 0;
+ is_assert = 0;
+ //usb_debug("LD_btmtk_usb_probe begin\n");
+ usb_debug("probe_counter = %d\n", probe_counter);
+
+ data = os_kzalloc(sizeof(*data), MTK_GFP_ATOMIC);
+ if (!data) {
+ usb_debug("[ERR] end Error 1\n");
+ return -ENOMEM;
+ }
+
+ data->hcif = BT_INST(dev)->hci_if;
+
+ data->cmdreq_type = USB_TYPE_CLASS;
+
+ data->udev = dev;
+
+ data->meta_tx = 0;
+
+ data->io_buf = os_kmalloc(LD_BT_MAX_EVENT_SIZE, MTK_GFP_ATOMIC);
+ if (!data->io_buf) {
+ usb_debug("Can't allocate memory io_buf\n");
+ os_kfree(data);
+ return -ENOMEM;
+ }
+
+ data->rom_patch_bin_file_name = os_kzalloc(MAX_BIN_FILE_NAME_LEN, MTK_GFP_ATOMIC);
+ if (!data->rom_patch_bin_file_name) {
+ usb_debug("Can't allocate memory rom_patch_bin_file_name\n");
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return -ENOMEM;
+ }
+
+ if (data->woble_setting_file_name == NULL) {
+ data->woble_setting_file_name = os_kzalloc(MAX_BIN_FILE_NAME_LEN, MTK_GFP_ATOMIC);
+ if (!data->woble_setting_file_name) {
+ usb_debug("Can't allocate memory woble_setting_file_name\n");
+ os_kfree(data->rom_patch_bin_file_name);
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return -ENOMEM;
+ }
+ }
+
+ btmtk_usb_switch_iobase(data, WLAN);
+ btmtk_usb_initialize_cfg_items(data);
+
+ /* clayton: according to the chip id, load f/w or rom patch */
+ usb_debug("data address = %p\n", data);
+ btmtk_usb_cap_init(data);
+
+ if(flag == 1){
+ ret = Ldbtusb_getBtWakeT(data);
+ os_kfree(data->woble_setting_file_name);
+ os_kfree(data->rom_patch_bin_file_name);
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return ret;
+ }
+
+ /* load bt.cfg */
+ btmtk_usb_load_bt_cfg(BT_CFG_NAME, data);
+
+ if (data->need_load_rom_patch) {
+ if (is_mt7668(data) || is_mt7663(data))
+ err = btmtk_usb_load_rom_patch_7668(data);
+ else if (is_mt7961(data) || is_mt7902(data))
+ err = btmtk_load_rom_patch_79xx(data);
+ else
+ err = btmtk_usb_load_rom_patch(data);
+ //btmtk_usb_send_hci_suspend_cmd(data);
+ if (err < 0) {
+ if (data->io_buf) os_kfree(data->io_buf);
+ if (data->rom_patch_bin_file_name) os_kfree(data->rom_patch_bin_file_name);
+ if (data->woble_setting_file_name) os_kfree(data->woble_setting_file_name);
+ os_kfree(data);
+ usb_debug("[ERR] end Error 2\n");
+ return err;
+ }
+ }
+
+ // Clean & free buffer
+ if (data->rom_patch_bin_file_name)
+ os_kfree(data->rom_patch_bin_file_name);
+
+
+ isUsbDisconnet = 0;
+ BT_INST(dev)->priv_data = data;
+ isbtready = 1;
+
+ //usb_debug("btmtk_usb_probe end\n");
+ return 0;
+}
+
+//---------------------------------------------------------------------------
+void LD_btmtk_usb_disconnect(mtkbt_dev_t *dev)
+{
+ struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+
+ usb_debug("\n");
+
+ if (!data)
+ return;
+
+ isbtready = 0;
+ metaCount = 0;
+
+ if (data->need_load_rom_patch)
+ os_kfree(data->rom_patch);
+
+ if (data->need_load_fw)
+ os_kfree(data->fw_image);
+
+ usb_debug("unregister bt irq\n");
+
+ isUsbDisconnet = 1;
+ usb_debug("btmtk: stop all URB\n");
+ os_kfree(data->io_buf);
+ os_kfree(data);
+}
+
+//---------------------------------------------------------------------------
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.h b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.h
new file mode 100644
index 0000000..a5f991b
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_btmtk_usb.h
@@ -0,0 +1,261 @@
+#ifndef __LD_BTMTK_USB_H__
+#define __LD_BTMTK_USB_H__
+
+#include "LD_usbbt.h"
+
+/* Memory map for MTK BT */
+//#if 0
+/* SYS Control */
+#define SYSCTL 0x400000
+
+/* WLAN */
+#define WLAN 0x410000
+
+/* MCUCTL */
+#define CLOCK_CTL 0x0708
+#define INT_LEVEL 0x0718
+#define COM_REG0 0x0730
+#define SEMAPHORE_00 0x07B0
+#define SEMAPHORE_01 0x07B4
+#define SEMAPHORE_02 0x07B8
+#define SEMAPHORE_03 0x07BC
+
+/* Chip definition */
+
+#define CONTROL_TIMEOUT_JIFFIES (300)
+#define DEVICE_VENDOR_REQUEST_OUT 0x40
+#define DEVICE_VENDOR_REQUEST_IN 0xc0
+#define DEVICE_CLASS_REQUEST_OUT 0x20
+#define DEVICE_CLASS_REQUEST_IN 0xa0
+
+#define BTUSB_MAX_ISOC_FRAMES 10
+#define BTUSB_INTR_RUNNING 0
+#define BTUSB_BULK_RUNNING 1
+#define BTUSB_ISOC_RUNNING 2
+#define BTUSB_SUSPENDING 3
+#define BTUSB_DID_ISO_RESUME 4
+
+/* ROM Patch */
+#define PATCH_HCI_HEADER_SIZE 4
+#define PATCH_WMT_HEADER_SIZE 5
+#define PATCH_HEADER_SIZE (PATCH_HCI_HEADER_SIZE + PATCH_WMT_HEADER_SIZE)
+#define UPLOAD_PATCH_UNIT 2048
+#define PATCH_INFO_SIZE 30
+#define PATCH_PHASE1 1
+#define PATCH_PHASE2 2
+#define PATCH_PHASE3 3
+#define PATCH_LEN_ILM (192 * 1024)
+
+#define BUZZARD_CHIP_ID 0x70010200
+#define BUZZARD_FLAVOR 0x70010020
+#define BUZZARD_FW_VERSION 0x80021004
+
+
+
+/**
+ * 0: patch download is not complete/BT get patch semaphore fail (WiFi get semaphore success)
+ * 1: patch download is complete
+ * 2: patch download is not complete/BT get patch semaphore success
+ */
+#define PATCH_ERR -1
+#define PATCH_IS_DOWNLOAD_BY_OTHER 0
+#define PATCH_READY 1
+#define PATCH_NEED_DOWNLOAD 2
+
+#define MAX_BIN_FILE_NAME_LEN 64
+#define LD_BT_MAX_EVENT_SIZE 260
+#define BD_ADDR_LEN 6
+
+#define WOBLE_SETTING_FILE_NAME_7961 "woble_setting_7961.bin"
+#define WOBLE_SETTING_FILE_NAME_7668 "woble_setting_7668.bin"
+#define WOBLE_SETTING_FILE_NAME_7663 "woble_setting_7663.bin"
+#define WOBLE_SETTING_FILE_NAME "woble_setting.bin"
+#define WOBLE_CFG_NAME_PREFIX "woble_setting"
+#define WOBLE_CFG_NAME_SUFFIX "bin"
+
+#define BT_CFG_NAME "bt.cfg"
+#define BT_CFG_NAME_PREFIX "bt_mt"
+#define BT_CFG_NAME_PREFIX_76XX "bt_"
+#define BT_CFG_NAME_SUFFIX "cfg"
+#define BT_UNIFY_WOBLE "SUPPORT_UNIFY_WOBLE"
+#define BT_UNIFY_WOBLE_TYPE "UNIFY_WOBLE_TYPE"
+#define BT_WMT_CMD "WMT_CMD"
+
+#define WMT_CMD_COUNT 255
+
+#define WAKE_DEV_RECORD "wake_on_ble.conf"
+#define WAKE_DEV_RECORD_PATH "misc/bluedroid"
+#define APCF_SETTING_COUNT 10
+#define WOBLE_SETTING_COUNT 10
+
+/* It is for mt7961 download rom patch*/
+#define FW_ROM_PATCH_HEADER_SIZE 32
+#define FW_ROM_PATCH_GD_SIZE 64
+#define FW_ROM_PATCH_SEC_MAP_SIZE 64
+#define SEC_MAP_NEED_SEND_SIZE 52
+#define PATCH_STATUS 6
+#define SECTION_SPEC_NUM 13
+
+/* this for 79XX need download patch staus
+ * 0:
+ * patch download is not complete, BT driver need to download patch
+ * 1:
+ * patch is downloading by Wifi,BT driver need to retry until status = PATCH_READY
+ * 2:
+ * patch download is complete, BT driver no need to download patch
+ */
+#define BUZZARD_PATCH_ERR -1
+#define BUZZARD_PATCH_NEED_DOWNLOAD 0
+#define BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER 1
+#define BUZZARD_PATCH_READY 2
+
+/* 0:
+ * using legacy wmt cmd/evt to download fw patch, usb/sdio just support 0 now
+ * 1:
+ * using DMA to download fw patch
+ */
+#define PATCH_DOWNLOAD_USING_WMT 0
+#define PATCH_DOWNLOAD_USING_DMA 1
+
+#define PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME 1
+#define PATCH_DOWNLOAD_PHASE1_2_RETRY 5
+#define PATCH_DOWNLOAD_PHASE3_DELAY_TIME 20
+#define PATCH_DOWNLOAD_PHASE3_RETRY 20
+
+
+enum {
+ BTMTK_EP_TYPE_OUT_CMD = 0, /*EP type out for hci cmd and wmt cmd */
+ BTMTK_EP_TPYE_OUT_ACL, /* EP type out for acl pkt with load rompatch */
+};
+
+
+typedef enum {
+ TYPE_APCF_CMD,
+} woble_setting_type;
+
+enum fw_cfg_index_len {
+ FW_CFG_INX_LEN_NONE = 0,
+ FW_CFG_INX_LEN_2 = 2,
+ FW_CFG_INX_LEN_3 = 3,
+};
+
+struct fw_cfg_struct {
+ u8 *content; /* APCF content or radio off content */
+ int length; /* APCF content or radio off content of length */
+};
+
+struct bt_cfg_struct {
+ u8 support_unify_woble; /* support unify woble or not */
+ u8 unify_woble_type; /* 0: legacy. 1: waveform. 2: IR */
+ struct fw_cfg_struct wmt_cmd[WMT_CMD_COUNT];
+};
+
+struct LD_btmtk_usb_data {
+ mtkbt_dev_t *udev; /* store the usb device informaiton */
+
+ unsigned long flags;
+ int meta_tx;
+ HC_IF *hcif;
+
+ u8 cmdreq_type;
+
+ unsigned int sco_num;
+ int isoc_altsetting;
+ int suspend_count;
+
+ /* request for different io operation */
+ u8 w_request;
+ u8 r_request;
+
+ /* io buffer for usb control transfer */
+ unsigned char *io_buf;
+
+ unsigned char *fw_image;
+ unsigned char *fw_header_image;
+
+ unsigned char *rom_patch;
+ unsigned char *rom_patch_header_image;
+ unsigned char *rom_patch_bin_file_name;
+ u32 chip_id;
+ unsigned int flavor;
+ unsigned int fw_version;
+ u8 need_load_fw;
+ u8 need_load_rom_patch;
+ u32 rom_patch_offset;
+ u32 rom_patch_len;
+ u32 fw_len;
+ int recv_evt_len;
+
+ u8 local_addr[BD_ADDR_LEN];
+ char *woble_setting_file_name;
+ u8 *setting_file;
+ u32 setting_file_len;
+ u8 *wake_dev; /* ADDR:NAP-UAP-LAP, VID/PID:Both Little endian */
+ u32 wake_dev_len;
+ struct fw_cfg_struct woble_setting_apcf[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac_location[WOBLE_SETTING_COUNT];
+
+ struct fw_cfg_struct woble_setting_radio_off;
+ struct fw_cfg_struct woble_setting_wakeup_type;
+ /* complete event */
+ struct fw_cfg_struct woble_setting_radio_off_comp_event;
+
+ struct bt_cfg_struct bt_cfg;
+};
+
+struct _PATCH_HEADER {
+ u8 ucDateTime[16];
+ u8 ucPlatform[4];
+ u16 u2HwVer;
+ u16 u2SwVer;
+ u32 u4MagicNum;
+};
+
+struct _Global_Descr {
+ u32 u4PatchVer;
+ u32 u4SubSys;
+ u32 u4FeatureOpt;
+ u32 u4SectionNum;
+};
+
+struct _Section_Map {
+ u32 u4SecType;
+ u32 u4SecOffset;
+ u32 u4SecSize;
+ union {
+ u32 u4SecSpec[SECTION_SPEC_NUM];
+ struct {
+ u32 u4DLAddr;
+ u32 u4DLSize;
+ u32 u4SecKeyIdx;
+ u32 u4AlignLen;
+ u32 reserved[9];
+ }bin_info_spec;
+ };
+};
+
+
+u8 LD_btmtk_usb_getWoBTW(void);
+int LD_btmtk_usb_probe(mtkbt_dev_t *dev,int flag);
+void LD_btmtk_usb_disconnect(mtkbt_dev_t *dev);
+void LD_btmtk_usb_SetWoble(mtkbt_dev_t *dev);
+int Ldbtusb_getBtWakeT(struct LD_btmtk_usb_data *data);
+
+
+#define REV_MT76x2E3 0x0022
+
+#define MT_REV_LT(_data, _chip, _rev) \
+ is_##_chip(_data) && (((_data)->chip_id & 0x0000ffff) < (_rev))
+
+#define MT_REV_GTE(_data, _chip, _rev) \
+ is_##_chip(_data) && (((_data)->chip_id & 0x0000ffff) >= (_rev))
+
+/*
+ * Load code method
+ */
+enum LOAD_CODE_METHOD {
+ BIN_FILE_METHOD,
+ HEADER_METHOD,
+};
+#endif
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/LD_errno.h b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_errno.h
new file mode 100644
index 0000000..b063749
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_errno.h
@@ -0,0 +1,154 @@
+/*
+ * U-boot - errno.h Error number defines
+ *
+ * Copyright (c) 2005-2007 Analog Devices Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+#ifndef _GENERIC_ERRNO_H
+#define _GENERIC_ERRNO_H
+
+#define EPERM 1 /* Operation not permitted */
+#define ENOENT 2 /* No such file or directory */
+#define ESRCH 3 /* No such process */
+#define EINTR 4 /* Interrupted system call */
+#define EIO 5 /* I/O error */
+#define ENXIO 6 /* No such device or address */
+#define E2BIG 7 /* Argument list too long */
+#define ENOEXEC 8 /* Exec format error */
+#define EBADF 9 /* Bad file number */
+#define ECHILD 10 /* No child processes */
+#define EAGAIN 11 /* Try again */
+#define ENOMEM 12 /* Out of memory */
+#define EACCES 13 /* Permission denied */
+#define EFAULT 14 /* Bad address */
+#define ENOTBLK 15 /* Block device required */
+#define EBUSY 16 /* Device or resource busy */
+#define EEXIST 17 /* File exists */
+#define EXDEV 18 /* Cross-device link */
+#define ENODEV 19 /* No such device */
+#define ENOTDIR 20 /* Not a directory */
+#define EISDIR 21 /* Is a directory */
+#define EINVAL 22 /* Invalid argument */
+#define ENFILE 23 /* File table overflow */
+#define EMFILE 24 /* Too many open files */
+#define ENOTTY 25 /* Not a typewriter */
+#define ETXTBSY 26 /* Text file busy */
+#define EFBIG 27 /* File too large */
+#define ENOSPC 28 /* No space left on device */
+#define ESPIPE 29 /* Illegal seek */
+#define EROFS 30 /* Read-only file system */
+#define EMLINK 31 /* Too many links */
+#define EPIPE 32 /* Broken pipe */
+#define EDOM 33 /* Math argument out of domain of func */
+#define ERANGE 34 /* Math result not representable */
+#define EDEADLK 35 /* Resource deadlock would occur */
+#define ENAMETOOLONG 36 /* File name too long */
+#define ENOLCK 37 /* No record locks available */
+#define ENOSYS 38 /* Function not implemented */
+#define ENOTEMPTY 39 /* Directory not empty */
+#define ELOOP 40 /* Too many symbolic links encountered */
+#define EWOULDBLOCK EAGAIN /* Operation would block */
+#define ENOMSG 42 /* No message of desired type */
+#define EIDRM 43 /* Identifier removed */
+#define ECHRNG 44 /* Channel number out of range */
+#define EL2NSYNC 45 /* Level 2 not synchronized */
+#define EL3HLT 46 /* Level 3 halted */
+#define EL3RST 47 /* Level 3 reset */
+#define ELNRNG 48 /* Link number out of range */
+#define EUNATCH 49 /* Protocol driver not attached */
+#define ENOCSI 50 /* No CSI structure available */
+#define EL2HLT 51 /* Level 2 halted */
+#define EBADE 52 /* Invalid exchange */
+#define EBADR 53 /* Invalid request descriptor */
+#define EXFULL 54 /* Exchange full */
+#define ENOANO 55 /* No anode */
+#define EBADRQC 56 /* Invalid request code */
+#define EBADSLT 57 /* Invalid slot */
+
+#define EDEADLOCK EDEADLK
+
+#define EBFONT 59 /* Bad font file format */
+#define ENOSTR 60 /* Device not a stream */
+#define ENODATA 61 /* No data available */
+#define ETIME 62 /* Timer expired */
+#define ENOSR 63 /* Out of streams resources */
+#define ENONET 64 /* Machine is not on the network */
+#define ENOPKG 65 /* Package not installed */
+#define EREMOTE 66 /* Object is remote */
+#define ENOLINK 67 /* Link has been severed */
+#define EADV 68 /* Advertise error */
+#define ESRMNT 69 /* Srmount error */
+#define ECOMM 70 /* Communication error on send */
+#define EPROTO 71 /* Protocol error */
+#define EMULTIHOP 72 /* Multihop attempted */
+#define EDOTDOT 73 /* RFS specific error */
+#define EBADMSG 74 /* Not a data message */
+#define EOVERFLOW 75 /* Value too large for defined data type */
+#define ENOTUNIQ 76 /* Name not unique on network */
+#define EBADFD 77 /* File descriptor in bad state */
+#define EREMCHG 78 /* Remote address changed */
+#define ELIBACC 79 /* Can not access a needed shared library */
+#define ELIBBAD 80 /* Accessing a corrupted shared library */
+#define ELIBSCN 81 /* .lib section in a.out corrupted */
+#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
+#define ELIBEXEC 83 /* Cannot exec a shared library directly */
+#define EILSEQ 84 /* Illegal byte sequence */
+#define ERESTART 85 /* Interrupted system call should be restarted */
+#define ESTRPIPE 86 /* Streams pipe error */
+#define EUSERS 87 /* Too many users */
+#define ENOTSOCK 88 /* Socket operation on non-socket */
+#define EDESTADDRREQ 89 /* Destination address required */
+#define EMSGSIZE 90 /* Message too long */
+#define EPROTOTYPE 91 /* Protocol wrong type for socket */
+#define ENOPROTOOPT 92 /* Protocol not available */
+#define EPROTONOSUPPORT 93 /* Protocol not supported */
+#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
+#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
+#define EPFNOSUPPORT 96 /* Protocol family not supported */
+#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
+#define EADDRINUSE 98 /* Address already in use */
+#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
+#define ENETDOWN 100 /* Network is down */
+#define ENETUNREACH 101 /* Network is unreachable */
+#define ENETRESET 102 /* Network dropped connection because of reset */
+#define ECONNABORTED 103 /* Software caused connection abort */
+#define ECONNRESET 104 /* Connection reset by peer */
+#define ENOBUFS 105 /* No buffer space available */
+#define EISCONN 106 /* Transport endpoint is already connected */
+#define ENOTCONN 107 /* Transport endpoint is not connected */
+#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
+#define ETOOMANYREFS 109 /* Too many references: cannot splice */
+#define ETIMEDOUT 110 /* Connection timed out */
+#define ECONNREFUSED 111 /* Connection refused */
+#define EHOSTDOWN 112 /* Host is down */
+#define EHOSTUNREACH 113 /* No route to host */
+#define EALREADY 114 /* Operation already in progress */
+#define EINPROGRESS 115 /* Operation now in progress */
+#define ESTALE 116 /* Stale NFS file handle */
+#define EUCLEAN 117 /* Structure needs cleaning */
+#define ENOTNAM 118 /* Not a XENIX named type file */
+#define ENAVAIL 119 /* No XENIX semaphores available */
+#define EISNAM 120 /* Is a named type file */
+#define EREMOTEIO 121 /* Remote I/O error */
+#define EDQUOT 122 /* Quota exceeded */
+#define ENOMEDIUM 123 /* No medium found */
+#define EMEDIUMTYPE 124 /* Wrong medium type */
+
+#endif
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.c b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.c
new file mode 100644
index 0000000..097df5c
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.c
@@ -0,0 +1,469 @@
+//#include <command.h>
+//#include <common.h>
+//#include <ShareType.h>
+//#include <CusConfig.h>
+//#include <MsVfs.h>
+//#include <MsDebug.h>
+//#include "usb_def.h"
+//#include <MsSystem.h>
+#include <stdio.h>
+#include "LD_usbbt.h"
+#include "LD_btmtk_usb.h"
+//#include "hal_usb.h"
+
+usb_vid_pid array_mtk_vid_pid[] = {
+ {0x0E8D, 0x7668, "MTK7668"}, // 7668
+ {0x0E8D, 0x76A0, "MTK7662T"}, // 7662T
+ {0x0E8D, 0x76A1, "MTK7632T"}, // 7632T
+ {0x0E8D, 0x7663, "MTK7663"}, //7663
+ {0x0E8D, 0x7961, "MTK7961"}, //7961
+ {0x0E8D, 0x7902, "MTK7902"}, //7902
+};
+
+int max_mtk_wifi_id = (sizeof(array_mtk_vid_pid) / sizeof(array_mtk_vid_pid[0]));
+usb_vid_pid *pmtk_wifi = &array_mtk_vid_pid[0];
+
+static mtkbt_dev_t *g_DrvData = NULL;
+
+extern void LDR_Mount(void);
+extern UINT32 FAT_getsize(const char* filename);
+extern UINT8 FAT_Read(const char* filename, char *buffer,UINT32 filesize);
+
+VOID *os_memcpy(VOID *dst, const VOID *src, UINT32 len)
+{
+ return x_memcpy(dst, src, len);
+}
+
+VOID *os_memmove(VOID *dest, const void *src,UINT32 len)
+{
+ return x_memcpy(dest, src, len);
+}
+
+VOID *os_memset(VOID *s, int c, size_t n)
+{
+ return x_memset(s,c,n);
+}
+
+VOID *os_kzalloc(size_t size, unsigned int flags)
+{
+ VOID *ptr = x_mem_alloc(size);
+
+ os_memset(ptr, 0, size);
+ return ptr;
+}
+
+void LD_load_code_from_bin(unsigned char **image, char *bin_name, char *path, mtkbt_dev_t *dev, u32 *code_len)
+{
+ int size;
+
+ size = FAT_getsize(bin_name);
+ if (size == -1){
+ usb_debug("Get file size fail\n");
+ return;
+ }
+
+ *code_len = size;
+ *image = x_mem_alloc(size);
+ FAT_Read(bin_name, (char *)(*image),size);
+ return;
+}
+
+static int usb_bt_bulk_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 *data,
+ int size,
+ int* realsize,
+ int timeout /* not used */
+)
+{
+ int ret =0 ;
+ if(dev == NULL || dev->idev == NULL || dev->bulk_tx_ep == NULL)
+ {
+ usb_debug("bulk out error 00\n");
+ return -1;
+ }
+
+ //usb_debug("[usb_bt_bulk_msg]ep_addr:%x\n", dev->bulk_tx_ep->bEndpointAddress);
+ //usb_debug("[usb_bt_bulk_msg]ep_maxpkt:%x\n", dev->bulk_tx_ep->wMaxPacketSize);
+
+ if(epType == MTKBT_BULK_TX_EP)
+ {
+ ret = dev->idev->controller->bulk(dev->bulk_tx_ep, size, data,0);
+ *realsize = ret;
+
+ if(ret<0)
+ {
+ usb_debug("bulk out error 01\n");
+ return -1;
+ }
+
+ if(*realsize == size)
+ {
+ //usb_debug("bulk out success 01,size =0x%x\n",size);
+ return 0;
+ }
+ else
+ {
+ usb_debug("bulk out fail 02,size =0x%x,realsize =0x%x\n",size,*realsize);
+ }
+ }
+ return -1;
+}
+
+static int usb_bt_control_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 request,
+ u8 requesttype,
+ u16 value,
+ u16 index,
+ u8 *data,
+ int data_length,
+ int timeout /* not used */
+)
+{
+ int ret = -1;
+ dev_req_t dr;
+ dr.bmRequestType = requesttype;
+ dr.bRequest = request;
+ dr.wValue = value;
+ dr.wIndex = index;
+ dr.wLength = data_length;
+ if(epType == MTKBT_CTRL_TX_EP)
+ {
+ ret = dev->idev->controller->control(dev->idev, OUT, sizeof (dr), &dr, data_length, data);
+ }
+ else if (epType == MTKBT_CTRL_RX_EP)
+ {
+ ret = dev->idev->controller->control(dev->idev, IN, sizeof (dr), &dr, data_length, data);
+ }
+ else
+ {
+ usb_debug("control message wrong Type =0x%x\n",epType);
+ }
+
+ if (ret < 0)
+ {
+ usb_debug("Err1(%d)\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int usb_bt_interrupt_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 *data,
+ int size,
+ int* realsize,
+ int timeout /* unit of 1ms */
+)
+{
+ int ret = -1;
+
+ usb_debug("epType = 0x%x\n",epType);
+
+ if(epType == MTKBT_INTR_EP)
+ {
+ ret = dev->idev->controller->intr(dev->intr_ep, size, realsize,data, 2000);
+ }
+
+ usb_debug("realsize=%d reqdata 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",*realsize,data[0],data[1],data[2],data[3],data[4],data[5]);
+
+ if(ret < 0 )
+ {
+ usb_debug("Err1(%d)\n", ret);
+ return ret;
+ }
+ //usb_debug("ret = 0x%x\n",ret);
+ return ret;
+}
+
+static HC_IF usbbt_host_interface =
+{
+ usb_bt_bulk_msg,
+ usb_bt_control_msg,
+ usb_bt_interrupt_msg,
+};
+
+static void Ldbtusb_diconnect (btusbdev_t *dev)
+{
+ LD_btmtk_usb_disconnect(g_DrvData);
+
+ if(g_DrvData)
+ {
+ os_kfree(g_DrvData);
+ }
+ g_DrvData = NULL;
+}
+
+static int Ldbtusb_SetWoble(btusbdev_t *dev)
+{
+ if(!g_DrvData)
+ {
+ usb_debug("usb set woble fail ,because no drv data\n");
+ return -1;
+ }
+ else
+ {
+ LD_btmtk_usb_SetWoble(g_DrvData);
+ usb_debug("usb set woble end\n");
+ }
+ return 0;
+}
+
+int Ldbtusb_connect (btusbdev_t *dev, int flag)
+{
+ int ret = 0;
+
+ // For Mstar
+ //struct usb_endpoint_descriptor *ep_desc;
+ //struct usb_interface *iface;
+ int i;
+ //iface = &dev->config.if_desc[0];
+
+ if(g_DrvData == NULL)
+ {
+ g_DrvData = os_kmalloc(sizeof(mtkbt_dev_t),MTK_GFP_ATOMIC);
+
+ if(!g_DrvData)
+ {
+ usb_debug("Not enough memory for mtkbt virtual usb device.\n");
+ return -1;
+ }
+ else
+ {
+ os_memset(g_DrvData,0,sizeof(mtkbt_dev_t));
+ g_DrvData->idev = dev;
+ g_DrvData->connect = Ldbtusb_connect;
+ g_DrvData->disconnect = Ldbtusb_diconnect;
+ g_DrvData->SetWoble = Ldbtusb_SetWoble;
+ }
+ }
+ else
+ {
+ return -1;
+ }
+
+ for (i = 1; i <= dev->num_endp; i++) {
+ usb_debug("dev->endpoints[%d].type = %d\n", i, dev->endpoints[i].type);
+ usb_debug("dev->endpoints[%d].endpoint = %d\n", i, dev->endpoints[i].endpoint);
+ usb_debug("dev->endpoints[%d].direction = %d\n", i, dev->endpoints[i].direction);
+ if (dev->endpoints[i].type == BULK)
+ {
+ if (dev->endpoints[i].direction == IN)
+ {
+ g_DrvData->bulk_rx_ep = &dev->endpoints[i];
+ }
+ else if (dev->endpoints[i].direction == OUT &&
+ dev->endpoints[i].endpoint != 0x01)
+ {
+ g_DrvData->bulk_tx_ep = &dev->endpoints[i];
+ }
+ continue;
+ }
+ if (dev->endpoints[i].type == INTERRUPT &&
+ dev->endpoints[i].endpoint != 0x8f)
+ {
+ g_DrvData->intr_ep = &dev->endpoints[i];
+ continue;
+ }
+ }
+
+ if (!g_DrvData->intr_ep || !g_DrvData->bulk_tx_ep || !g_DrvData->bulk_rx_ep)
+ {
+ os_kfree(g_DrvData);
+ g_DrvData = NULL;
+ usb_debug("btmtk_usb_probe end Error 3\n");
+ return -1;
+ }
+
+ /* Init HostController interface */
+ g_DrvData->hci_if = &usbbt_host_interface;
+
+ /* btmtk init */
+ ret = LD_btmtk_usb_probe(g_DrvData, flag);
+
+ if (ret != 0)
+ {
+ usb_debug("usb probe fail\n");
+ if(g_DrvData)
+ {
+ os_kfree(g_DrvData);
+ }
+ g_DrvData = NULL;
+ return -1;
+ }
+ else
+ {
+ usb_debug("usbbt probe success\n");
+ }
+ return ret;
+}
+
+u8 LDbtusb_getWoBTW(void)
+{
+ return LD_btmtk_usb_getWoBTW();
+}
+#if 0
+static int checkUsbDevicePort(struct usb_device* udev, u16 vendorID, u16 productID, u8 port)
+{
+ struct usb_device* pdev = NULL;
+/*#if defined (CONFIG_USB_PREINIT)
+ usb_stop(port);
+ if (usb_post_init(port) == 0)
+#else
+ if (usb_init(port) == 0)
+#endif*/
+ #if 0
+ {
+ /* get device */
+ //pdev = usb_get_dev_index(0);
+ if ((pdev != NULL) && (pdev->descriptor.idVendor == vendorID) && (pdev->descriptor.idProduct == productID)) // MTK 7662
+ {
+ Printf("OK\n");
+ x_memcpy(udev, pdev, sizeof(struct usb_device));
+ return 0 ;
+ }
+ }
+ #endif
+ return -1;
+}
+#endif
+#if 0
+static int findUsbDevice(struct usb_device* udev)
+{
+ int ret = -1;
+ u8 idx = 0;
+ u8 i = 0;
+ char portNumStr[10] = "\0";
+ char* pBTUsbPort = NULL;
+ Printf("IN\n");
+ if(udev == NULL)
+ {
+ Printf("udev can not be NULL\n");
+ return -1;
+ }
+ //use the usb poll function replace----lining
+ //keys add:find usb port idx
+ /*#define BT_USB_PORT "bt_usb_port"
+ pBTUsbPort = getenv(BT_USB_PORT);
+ if(pBTUsbPort != NULL)
+ {
+ i = 0;
+ // search mtk bt usb port
+ idx = atoi(pBTUsbPort);
+ usb_debug("find mtk bt usb device from usb prot[%d]\n", idx);
+ while (i < max_mtk_wifi_id) {
+ ret = checkUsbDevicePort(udev, (pmtk_wifi + i)->vid, (pmtk_wifi + i)->pid, idx);
+ if (ret == 0) break;
+ i++;
+ #if defined(NEW_RC_CON) && (NEW_RC_CON == TRUE)
+ usb_debug("fengchen 7668 error");
+ return -1;
+ #endif
+ }
+ if(ret == 0)
+ {
+ return 0;
+ }
+ }*/
+ //keys add:find usb port idx end!!!
+ // not find mt bt usb device from given usb port, so poll every usb port.
+ /*#if defined(ENABLE_FIFTH_EHC)
+ const char u8UsbPortCount = 5;
+ #elif defined(ENABLE_FOURTH_EHC)
+ const char u8UsbPortCount = 4;
+ #elif defined(ENABLE_THIRD_EHC)
+ const char u8UsbPortCount = 3;
+ #elif defined(ENABLE_SECOND_EHC)
+ const char u8UsbPortCount = 2;
+ #else
+ const char u8UsbPortCount = 1;
+ #endif
+ for(idx = 0; idx < u8UsbPortCount; idx++)
+ {
+ i = 0;
+ while (i < max_mtk_wifi_id) {
+ ret = checkUsbDevicePort(udev, (pmtk_wifi + i)->vid, (pmtk_wifi + i)->pid, idx);
+ if (ret == 0) break;
+ i++;
+ }
+ if(ret == 0)
+ {
+ // set bt_usb_port to store mt bt usb device port
+ snprintf(portNumStr, sizeof(portNumStr), "%d", idx);
+ setenv(BT_USB_PORT, portNumStr);
+ saveenv();
+ return 0;
+ }
+ }
+ if(pBTUsbPort != NULL)
+ {
+ // env BT_USB_PORT is involid, so delete it
+ setenv(BT_USB_PORT, NULL);
+ saveenv();
+ }*/
+ Printf("Not find usb device\n");
+ return -1;
+}
+#endif
+
+void do_setMtkBT(usbdev_t *dev)
+{
+ int ret = 0;
+ Printf("IN\n");
+ LDR_Mount(); //16566
+ // MTK USB controller
+ /*ret = findUsbDevice(&udev);
+ if (ret != 0)
+ {
+ Printf("find bt usb device failed\n");
+ return -1;
+ }*/
+ ret = Ldbtusb_connect(dev,0);
+ if(ret != 0){
+ Printf("connect to bt usb device failed\n");
+ return;
+ }
+ ret = Ldbtusb_SetWoble(NULL);
+ if(ret != 0)
+ {
+ Printf("set bt usb device woble cmd failed\n");
+ return;
+ }
+ Printf("OK\n");
+}
+
+int getMtkBTWakeT(void)
+{
+ int ret = 0;
+ #if 0
+ struct usb_device udev;
+ memset(&udev, 0, sizeof(struct usb_device));
+ Printf("IN\n");
+ // MTK USB controller
+ ret = findUsbDevice(&udev);
+ if (ret != 0)
+ {
+ Printf("find bt usb device failed\n");
+ return -1;
+ }
+ ret = Ldbtusb_connect(&udev, 1);
+ if(ret != 0)
+ {
+ Printf("connect to bt usb device failed\n");
+ return -1;
+ }
+
+ if(ret != 0)
+ {
+ Printf("set bt usb device woble cmd failed\n");
+ return -1;
+ }
+ Printf("OK\n");
+ #endif
+ return ret;
+}
+
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.h b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.h
new file mode 100644
index 0000000..54e9cde
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/LD_usbbt.h
@@ -0,0 +1,139 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2008-2010 coresystems GmbH
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __LD_USBBT_H__
+#define __LD_USBBT_H__
+//#include <common.h>
+//#include "usb_def.h"
+//#include <MsTypes.h>
+
+#include "types.h"
+#include "loader_if.h"
+#include "usb_type.h"
+
+struct _usb_vid_pid_
+{
+ unsigned short vid;
+ unsigned short pid;
+ char name[10];
+};
+typedef struct _usb_vid_pid_ usb_vid_pid;
+
+#define MTKBT_CTRL_TX_EP 0
+#define MTKBT_CTRL_RX_EP 1
+#define MTKBT_INTR_EP 2
+#define MTKBT_BULK_TX_EP 3
+#define MTKBT_BULK_RX_EP 4
+
+#define MTK_GFP_ATOMIC 1
+
+#define CRC_CHECK 0
+
+#define BTLDER "[BT-LOADER] "
+#define USB_TYPE_STANDARD (0x00 << 5)
+#define USB_TYPE_CLASS (0x01 << 5)
+#define USB_TYPE_VENDOR (0x02 << 5)
+#define USB_TYPE_RESERVED (0x03 << 5)
+
+
+#define usb_debug(fmt,...) Printf("%s: "fmt, __func__, ##__VA_ARGS__)
+#define usb_debug_raw(p, l, fmt, ...) \
+ do { \
+ int raw_count = 0; \
+ const unsigned char *ptr = p; \
+ Printf("%s: "fmt, __func__, ##__VA_ARGS__); \
+ for (raw_count = 0; raw_count < l; ++raw_count) \
+ Printf(" %02X", ptr[raw_count]); \
+ Printf("\n"); \
+ } while (0)
+
+#define os_kmalloc(size,flags) x_mem_alloc(size)
+#define os_kfree(ptr) x_mem_free(ptr)
+
+#define MTK_UDELAY(x) HAL_Delay_us(x)
+#define MTK_MDELAY(x) HAL_Delay_us(x*1000)
+
+//#define btusbdev_t struct usb_interface
+#define btusbdev_t struct usbdev
+
+#undef NULL
+#define NULL ((void *)0)
+#define s32 signed int
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+typedef unsigned int UINT32;
+typedef signed int INT32;
+typedef unsigned char UINT8;
+typedef unsigned long ULONG;
+typedef unsigned char BOOL;
+
+typedef struct __USBBT_DEVICE__ mtkbt_dev_t;
+
+typedef struct {
+ int (*usb_bulk_msg) (mtkbt_dev_t *dev, u32 epType, u8 *data, int size, int* realsize, int timeout);
+ int (*usb_control_msg) (mtkbt_dev_t *dev, u32 epType, u8 request, u8 requesttype, u16 value, u16 index,
+ u8 *data, int data_length, int timeout);
+ int (*usb_interrupt_msg)(mtkbt_dev_t *dev, u32 epType, u8 *data, int size, int* realsize, int timeout);
+} HC_IF;
+
+struct __USBBT_DEVICE__
+{
+ void *priv_data;
+ btusbdev_t* intf;
+ struct usbdev *idev;
+ endpoint_t *intr_ep;
+ endpoint_t *bulk_tx_ep;
+ endpoint_t *bulk_rx_ep;
+ endpoint_t *isoc_tx_ep;
+ endpoint_t *isoc_rx_ep;
+ HC_IF *hci_if;
+ int (*connect)(btusbdev_t *dev, int flag);
+ void (*disconnect)(btusbdev_t *dev);
+ int (*SetWoble)(btusbdev_t *dev);
+};//mtkbt_dev_t;
+
+#define BT_INST(dev) (dev)
+
+u8 LDbtusb_getWoBTW(void);
+int Ldbtusb_connect (btusbdev_t *dev,int falg);
+VOID *os_memcpy(VOID *dst, const VOID *src, UINT32 len);
+VOID *os_memmove(VOID *dest, const void *src,UINT32 len);
+VOID *os_memset(VOID *s, int c, size_t n);
+VOID *os_kzalloc(size_t size, unsigned int flags);
+void LD_load_code_from_bin(unsigned char **image, char *bin_name, char *path, mtkbt_dev_t *dev,u32 *code_len);
+
+void do_setMtkBT(usbdev_t *dev);
+int getMtkBTWakeT(void);
+
+#endif
diff --git a/uboot_driver/MTK/mtk-bt/mt7xxx/Makefile b/uboot_driver/MTK/mtk-bt/mt7xxx/Makefile
new file mode 100644
index 0000000..0d70ff3
--- /dev/null
+++ b/uboot_driver/MTK/mtk-bt/mt7xxx/Makefile
@@ -0,0 +1,156 @@
+###############################################################################
+# Copyright Statement: #
+# #
+# This software/firmware and related documentation ("MediaTek Software") #
+# are protected under international and related jurisdictions'copyright laws #
+# as unpublished works. The information contained herein is confidential and #
+# proprietary to MediaTek Inc. Without the prior written permission of #
+# MediaTek Inc., any reproduction, modification, use or disclosure of #
+# MediaTek Software, and information contained herein, in whole or in part, #
+# shall be strictly prohibited. #
+# MediaTek Inc. Copyright (C) 2010. All rights reserved. #
+# #
+# BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND #
+# AGREES TO THE FOLLOWING: #
+# #
+# 1)Any and all intellectual property rights (including without #
+# limitation, patent, copyright, and trade secrets) in and to this #
+# Software/firmware and related documentation ("MediaTek Software") shall #
+# remain the exclusive property of MediaTek Inc. Any and all intellectual #
+# property rights (including without limitation, patent, copyright, and #
+# trade secrets) in and to any modifications and derivatives to MediaTek #
+# Software, whoever made, shall also remain the exclusive property of #
+# MediaTek Inc. Nothing herein shall be construed as any transfer of any #
+# title to any intellectual property right in MediaTek Software to Receiver. #
+# #
+# 2)This MediaTek Software Receiver received from MediaTek Inc. and/or its #
+# representatives is provided to Receiver on an "AS IS" basis only. #
+# MediaTek Inc. expressly disclaims all warranties, expressed or implied, #
+# including but not limited to any implied warranties of merchantability, #
+# non-infringement and fitness for a particular purpose and any warranties #
+# arising out of course of performance, course of dealing or usage of trade. #
+# MediaTek Inc. does not provide any warranty whatsoever with respect to the #
+# software of any third party which may be used by, incorporated in, or #
+# supplied with the MediaTek Software, and Receiver agrees to look only to #
+# such third parties for any warranty claim relating thereto. Receiver #
+# expressly acknowledges that it is Receiver's sole responsibility to obtain #
+# from any third party all proper licenses contained in or delivered with #
+# MediaTek Software. MediaTek is not responsible for any MediaTek Software #
+# releases made to Receiver's specifications or to conform to a particular #
+# standard or open forum. #
+# #
+# 3)Receiver further acknowledge that Receiver may, either presently #
+# and/or in the future, instruct MediaTek Inc. to assist it in the #
+# development and the implementation, in accordance with Receiver's designs, #
+# of certain softwares relating to Receiver's product(s) (the "Services"). #
+# Except as may be otherwise agreed to in writing, no warranties of any #
+# kind, whether express or implied, are given by MediaTek Inc. with respect #
+# to the Services provided, and the Services are provided on an "AS IS" #
+# basis. Receiver further acknowledges that the Services may contain errors #
+# that testing is important and it is solely responsible for fully testing #
+# the Services and/or derivatives thereof before they are used, sublicensed #
+# or distributed. Should there be any third party action brought against #
+# MediaTek Inc. arising out of or relating to the Services, Receiver agree #
+# to fully indemnify and hold MediaTek Inc. harmless. If the parties #
+# mutually agree to enter into or continue a business relationship or other #
+# arrangement, the terms and conditions set forth herein shall remain #
+# effective and, unless explicitly stated otherwise, shall prevail in the #
+# event of a conflict in the terms in any agreements entered into between #
+# the parties. #
+# #
+# 4)Receiver's sole and exclusive remedy and MediaTek Inc.'s entire and #
+# cumulative liability with respect to MediaTek Software released hereunder #
+# will be, at MediaTek Inc.'s sole discretion, to replace or revise the #
+# MediaTek Software at issue. #
+# #
+# 5)The transaction contemplated hereunder shall be construed in #
+# accordance with the laws of Singapore, excluding its conflict of laws #
+# principles. Any disputes, controversies or claims arising thereof and #
+# related thereto shall be settled via arbitration in Singapore, under the #
+# then current rules of the International Chamber of Commerce (ICC). The #
+# arbitration shall be conducted in English. The awards of the arbitration #
+# shall be final and binding upon both parties and shall be entered and #
+# enforceable in any court of competent jurisdiction. #
+###############################################################################
+###########################################################################
+# $RCSfile: Makefile,v $
+# $Revision: #2 $
+# $Date: 2009/04/08 $
+# $Author: allen.kao $
+#
+# Description:
+# Leave-level makefile to build the subcomponent of driver library.
+#
+# Specify the source files to be compile in SRC.
+#############################################################################
+
+THIS_COMPONENT = usb3
+
+ifeq "$(UBOOT_LIBRARY)" "y"
+
+include $(TOPDIR)/config.mk
+
+CPPFLAGS += -I$(TOPDIR)/board/$(BOARDDIR)/drv_lib/drv_inc -I$(TOPDIR)/board/$(BOARDDIR)/drv_lib/inc
+CFLAGS += -I$(TOPDIR)/board/$(BOARDDIR)/drv_lib/drv_inc -I$(TOPDIR)/board/$(BOARDDIR)/drv_lib/inc
+CPPFLAGS += -I$(TOPDIR)/board/$(BOARDDIR)/include -I$(OSAI_INC)
+CFLAGS += -I$(TOPDIR)/board/$(BOARDDIR)/include -I$(OSAI_INC)
+
+LIB = lib$(THIS_COMPONENT).a
+
+
+OBJS := LD_btmtk_usb.o LD_usbbt.o
+
+$(LIB): $(OBJS) $(SOBJS)
+ $(AR) crv $@ $^
+
+clean:
+ rm -f $(SOBJS) $(OBJS)
+
+distclean: clean
+ rm -f $(LIB) core *.bak .depend
+
+#########################################################################
+
+.depend: Makefile $(SOBJS:.o=.S) $(OBJS:.o=.c)
+ $(CC) -M $(CPPFLAGS) $(SOBJS:.o=.S) $(OBJS:.o=.c) > $@
+
+-include .depend
+
+else # UBOOT_LIBRARY
+
+ifdef LINUX_DRV_ROOT
+export DRV_ROOT = $(LINUX_DRV_ROOT)
+else
+export DRV_ROOT = $(TARGET_OPEN_ROOT)
+endif
+
+
+SRC = LD_btmtk_usb.c LD_usbbt.c
+
+ifeq "$(USRDRV)" "true"
+ifeq "$(BUILD_LINUX_LOADER)" ""
+SRC += kcu_graphic.c
+endif
+endif
+
+OBJ =
+
+SUB_COMPONENTS =
+
+OPTIONAL_SUB_COMPONENTS =
+
+DEFINES +=
+
+CC_INC += -I$(KERNEL_ROOT)/$(KERNEL_VER)/include -I$(DRV_ROOT)/usb3/libpayload_usb/
+
+
+#############################################################################
+#
+# Include the makefile common to all components
+#
+
+include $(DRV_ROOT)/driver.mak
+
+
+endif # UBOOT_LIBRARY
+
diff --git a/uboot_driver/Mstar/include/mtk-bt/LD_btmtk_usb.h b/uboot_driver/Mstar/include/mtk-bt/LD_btmtk_usb.h
new file mode 100644
index 0000000..876bf71
--- /dev/null
+++ b/uboot_driver/Mstar/include/mtk-bt/LD_btmtk_usb.h
@@ -0,0 +1,317 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/******************************************************************************
+ *
+ * This file is provided under a dual license. When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+#ifndef __LD_BTMTK_USB_H__
+#define __LD_BTMTK_USB_H__
+
+#include <mtk-bt/LD_usbbt.h>
+
+/* Memory map for MTK BT */
+//#if 0
+/* SYS Control */
+#define SYSCTL 0x400000
+
+/* WLAN */
+#define WLAN 0x410000
+
+/* MCUCTL */
+#define CLOCK_CTL 0x0708
+#define INT_LEVEL 0x0718
+#define COM_REG0 0x0730
+#define SEMAPHORE_00 0x07B0
+#define SEMAPHORE_01 0x07B4
+#define SEMAPHORE_02 0x07B8
+#define SEMAPHORE_03 0x07BC
+
+/* Chip definition */
+
+#define CONTROL_TIMEOUT_JIFFIES (300)
+#define DEVICE_VENDOR_REQUEST_OUT 0x40
+#define DEVICE_VENDOR_REQUEST_IN 0xc0
+#define DEVICE_CLASS_REQUEST_OUT 0x20
+#define DEVICE_CLASS_REQUEST_IN 0xa0
+
+#define BTUSB_MAX_ISOC_FRAMES 10
+#define BTUSB_INTR_RUNNING 0
+#define BTUSB_BULK_RUNNING 1
+#define BTUSB_ISOC_RUNNING 2
+#define BTUSB_SUSPENDING 3
+#define BTUSB_DID_ISO_RESUME 4
+
+/* ROM Patch */
+#define PATCH_HCI_HEADER_SIZE_BULK_EP 4
+#define PATCH_HCI_HEADER_SIZE_CTRL_EP 3
+#define PATCH_WMT_HEADER_SIZE 5
+#define PATCH_HEADER_SIZE_BULK_EP (PATCH_WMT_HEADER_SIZE + PATCH_HCI_HEADER_SIZE_BULK_EP)
+#define PATCH_HEADER_SIZE_CTRL_EP (PATCH_WMT_HEADER_SIZE + PATCH_HCI_HEADER_SIZE_CTRL_EP)
+#define UPLOAD_PATCH_UNIT 512
+#define PATCH_INFO_SIZE 30
+#define PATCH_PHASE1 1
+#define PATCH_PHASE2 2
+#define PATCH_PHASE3 3
+#define PATCH_LEN_ILM (192 * 1024)
+
+#define BUZZARD_CHIP_ID 0x70010200
+#define BUZZARD_FLAVOR 0x70010020
+#define BUZZARD_FW_VERSION 0x80021004
+
+/**
+ * 0: patch download is not complete/BT get patch semaphore fail (WiFi get semaphore success)
+ * 1: patch download is complete
+ * 2: patch download is not complete/BT get patch semaphore success
+ */
+#define PATCH_ERR -1
+#define PATCH_IS_DOWNLOAD_BY_OTHER 0
+#define PATCH_READY 1
+#define PATCH_NEED_DOWNLOAD 2
+
+#define MAX_BIN_FILE_NAME_LEN 64
+#define LD_BT_MAX_EVENT_SIZE 260
+#define BD_ADDR_LEN 6
+
+#define WOBLE_SETTING_FILE_NAME_7961 "woble_setting_7961.bin"
+#define WOBLE_SETTING_FILE_NAME_7668 "woble_setting_7668.bin"
+#define WOBLE_SETTING_FILE_NAME_7663 "woble_setting_7663.bin"
+#define WOBLE_SETTING_FILE_NAME "woble_setting.bin"
+#define BT_CFG_NAME "bt.cfg"
+#define BT_CFG_NAME_PREFIX "bt_mt"
+#define BT_CFG_NAME_SUFFIX "cfg"
+#define BT_UNIFY_WOBLE "SUPPORT_UNIFY_WOBLE"
+#define BT_UNIFY_WOBLE_TYPE "UNIFY_WOBLE_TYPE"
+#define BT_WMT_CMD "WMT_CMD"
+
+#define WMT_CMD_COUNT 255
+
+#define WAKE_DEV_RECORD "wake_on_ble.conf"
+#define WAKE_DEV_RECORD_PATH "misc/bluedroid"
+#define APCF_SETTING_COUNT 10
+#define WOBLE_SETTING_COUNT 10
+
+/* It is for mt7961 download rom patch*/
+#define FW_ROM_PATCH_HEADER_SIZE 32
+#define FW_ROM_PATCH_GD_SIZE 64
+#define FW_ROM_PATCH_SEC_MAP_SIZE 64
+#define SEC_MAP_NEED_SEND_SIZE 52
+#define PATCH_STATUS 6
+#define SECTION_SPEC_NUM 13
+#define WMT_HEADER_LEN 4
+#define LOAD_PATCH_PHASE_LEN 1
+
+/* this for 79XX need download patch staus
+ * 0:
+ * patch download is not complete, BT driver need to download patch
+ * 1:
+ * patch is downloading by Wifi,BT driver need to retry until status = PATCH_READY
+ * 2:
+ * patch download is complete, BT driver no need to download patch
+ */
+#define BUZZARD_PATCH_ERR -1
+#define BUZZARD_PATCH_NEED_DOWNLOAD 0
+#define BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER 1
+#define BUZZARD_PATCH_READY 2
+
+/* 0:
+ * using legacy wmt cmd/evt to download fw patch, usb/sdio just support 0 now
+ * 1:
+ * using DMA to download fw patch
+ */
+#define PATCH_DOWNLOAD_USING_WMT 0
+#define PATCH_DOWNLOAD_USING_DMA 1
+
+
+#define PATCH_DOWNLOAD_CMD_DELAY_TIME 5
+#define PATCH_DOWNLOAD_CMD_RETRY 0
+#define PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME 1
+#define PATCH_DOWNLOAD_PHASE1_2_RETRY 5
+#define PATCH_DOWNLOAD_PHASE3_DELAY_TIME 20
+#define PATCH_DOWNLOAD_PHASE3_RETRY 20
+
+#define PM_SOURCE_DISABLE (0xFF)
+
+enum {
+ BTMTK_EP_TYPE_OUT_CMD = 0, /*EP type out for hci cmd and wmt cmd */
+ BTMTK_EP_TPYE_OUT_ACL, /* EP type out for acl pkt with load rompatch */
+};
+
+typedef enum {
+ TYPE_APCF_CMD,
+} woble_setting_type;
+
+enum fw_cfg_index_len {
+ FW_CFG_INX_LEN_NONE = 0,
+ FW_CFG_INX_LEN_2 = 2,
+ FW_CFG_INX_LEN_3 = 3,
+};
+
+struct fw_cfg_struct {
+ u8 *content; /* APCF content or radio off content */
+ int length; /* APCF content or radio off content of length */
+};
+
+#define UNIFY_WOBLE_LEGACY 0
+#define UNIFY_WOBLE_WAVEFORM 1
+struct bt_cfg_struct {
+ u8 support_unify_woble; /* support unify woble or not */
+ u8 unify_woble_type; /* 0: legacy. 1: waveform. 2: IR */
+ struct fw_cfg_struct wmt_cmd[WMT_CMD_COUNT];
+};
+
+struct LD_btmtk_usb_data {
+ mtkbt_dev_t *udev; /* store the usb device informaiton */
+
+ unsigned long flags;
+ int meta_tx;
+ HC_IF *hcif;
+
+ u8 cmdreq_type;
+
+ unsigned int sco_num;
+ int isoc_altsetting;
+ int suspend_count;
+
+ /* request for different io operation */
+ u8 w_request;
+ u8 r_request;
+
+ /* io buffer for usb control transfer */
+ unsigned char *io_buf;
+
+ unsigned char *fw_image;
+ unsigned char *fw_header_image;
+ unsigned char *fw_bin_file_name;
+
+ unsigned char *rom_patch;
+ unsigned char *rom_patch_header_image;
+ unsigned char *rom_patch_bin_file_name;
+ u32 chip_id;
+ unsigned int flavor;
+ unsigned int fw_version;
+ u8 need_load_fw;
+ u8 need_load_rom_patch;
+ u32 rom_patch_offset;
+ u32 rom_patch_len;
+ u32 fw_len;
+ int recv_evt_len;
+
+ u8 local_addr[BD_ADDR_LEN];
+ char *woble_setting_file_name;
+ u8 *setting_file;
+ u32 setting_file_len;
+ u8 *wake_dev; /* ADDR:NAP-UAP-LAP, VID/PID:Both Little endian */
+ u32 wake_dev_len;
+ struct fw_cfg_struct woble_setting_apcf[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac[WOBLE_SETTING_COUNT];
+ struct fw_cfg_struct woble_setting_apcf_fill_mac_location[WOBLE_SETTING_COUNT];
+
+ struct fw_cfg_struct woble_setting_radio_off;
+ struct fw_cfg_struct woble_setting_wakeup_type;
+ /* complete event */
+ struct fw_cfg_struct woble_setting_radio_off_comp_event;
+
+ struct bt_cfg_struct bt_cfg;
+};
+
+struct _PATCH_HEADER {
+ u8 ucDateTime[16];
+ u8 ucPlatform[4];
+ u16 u2HwVer;
+ u16 u2SwVer;
+ u32 u4MagicNum;
+};
+
+struct _Global_Descr {
+ u32 u4PatchVer;
+ u32 u4SubSys;
+ u32 u4FeatureOpt;
+ u32 u4SectionNum;
+};
+
+struct _Section_Map {
+ u32 u4SecType;
+ u32 u4SecOffset;
+ u32 u4SecSize;
+ union {
+ u32 u4SecSpec[SECTION_SPEC_NUM];
+ struct {
+ u32 u4DLAddr;
+ u32 u4DLSize;
+ u32 u4SecKeyIdx;
+ u32 u4AlignLen;
+ u32 reserved[9];
+ }bin_info_spec;
+ };
+};
+
+u8 LD_btmtk_usb_getWoBTW(void);
+int LD_btmtk_usb_probe(mtkbt_dev_t *dev, int flag);
+void LD_btmtk_usb_disconnect(mtkbt_dev_t *dev);
+void LD_btmtk_usb_SetWoble(mtkbt_dev_t *dev);
+int Ldbtusb_getBtWakeT(struct LD_btmtk_usb_data *data);
+
+
+#define REV_MT76x2E3 0x0022
+
+#define MT_REV_LT(_data, _chip, _rev) \
+ is_##_chip(_data) && (((_data)->chip_id & 0x0000ffff) < (_rev))
+
+#define MT_REV_GTE(_data, _chip, _rev) \
+ is_##_chip(_data) && (((_data)->chip_id & 0x0000ffff) >= (_rev))
+
+/*
+ * Load code method
+ */
+enum LOAD_CODE_METHOD {
+ BIN_FILE_METHOD,
+ HEADER_METHOD,
+};
+#endif
diff --git a/uboot_driver/Mstar/include/mtk-bt/LD_usbbt.h b/uboot_driver/Mstar/include/mtk-bt/LD_usbbt.h
new file mode 100644
index 0000000..0b516b6
--- /dev/null
+++ b/uboot_driver/Mstar/include/mtk-bt/LD_usbbt.h
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/******************************************************************************
+ *
+ * This file is provided under a dual license. When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2008-2010 coresystems GmbH
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __LD_USBBT_H__
+#define __LD_USBBT_H__
+#include <common.h>
+#include <malloc.h>
+#include <usb.h>
+#include <MsTypes.h>
+
+#define MTKBT_CTRL_TX_EP 0
+#define MTKBT_CTRL_RX_EP 1
+#define MTKBT_INTR_EP 2
+#define MTKBT_BULK_TX_EP 3
+#define MTKBT_BULK_RX_EP 4
+
+#define USB_INTR_MSG_TIMO 2000
+
+#define MTK_GFP_ATOMIC 1
+
+#define CRC_CHECK 0
+#define BT_USB_PORT "bt_usb_port"
+
+#define BTLDER "[BT-LOADER] "
+#define USB_TYPE_STANDARD (0x00 << 5)
+#define USB_TYPE_CLASS (0x01 << 5)
+#define USB_TYPE_VENDOR (0x02 << 5)
+#define USB_TYPE_RESERVED (0x03 << 5)
+
+
+#define usb_debug(fmt,...) printf("%s: "fmt, __func__, ##__VA_ARGS__)
+#define usb_debug_raw(p, l, fmt, ...) \
+ do { \
+ int raw_count = 0; \
+ const unsigned char *ptr = p; \
+ printf("%s: "fmt, __func__, ##__VA_ARGS__); \
+ for (raw_count = 0; raw_count < l; ++raw_count) \
+ printf(" %02X", ptr[raw_count]); \
+ printf("\n"); \
+ } while (0)
+
+#define os_kmalloc(size,flags) malloc(size)
+#define os_kfree(ptr) free(ptr)
+
+#define MTK_UDELAY(x) udelay(x)
+#define MTK_MDELAY(x) mdelay(x)
+
+
+//#define btusbdev_t struct usb_interface
+#define btusbdev_t struct usb_device
+
+#undef NULL
+#define NULL ((void *)0)
+#define s32 signed int
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+typedef unsigned int UINT32;
+typedef signed int INT32;
+typedef unsigned char UINT8;
+typedef unsigned long ULONG;
+typedef unsigned char BOOL;
+
+typedef struct __USBBT_DEVICE__ mtkbt_dev_t;
+
+typedef struct {
+ int (*usb_bulk_msg) (mtkbt_dev_t *dev, u32 epType, u8 *data, int size, int* realsize, int timeout);
+ int (*usb_control_msg) (mtkbt_dev_t *dev, u32 epType, u8 request, u8 requesttype, u16 value, u16 index,
+ u8 *data, int data_length, int timeout);
+ int (*usb_interrupt_msg)(mtkbt_dev_t *dev, u32 epType, u8 *data, int size, int* realsize, int timeout);
+} HC_IF;
+
+struct __USBBT_DEVICE__
+{
+ void *priv_data;
+ btusbdev_t* intf;
+ struct usb_device *udev;
+ struct usb_endpoint_descriptor *intr_ep;
+ struct usb_endpoint_descriptor *bulk_tx_ep;
+ struct usb_endpoint_descriptor *bulk_rx_ep;
+ struct usb_endpoint_descriptor *isoc_tx_ep;
+ struct usb_endpoint_descriptor *isoc_rx_ep;
+ HC_IF *hci_if;
+ int (*connect)(btusbdev_t *dev, int flag);
+ void (*disconnect)(btusbdev_t *dev);
+ int (*SetWoble)(btusbdev_t *dev);
+ u32 chipid;
+};//mtkbt_dev_t;
+
+#define BT_INST(dev) (dev)
+
+u8 LDbtusb_getWoBTW(void);
+int Ldbtusb_connect (btusbdev_t *dev, int falg);
+VOID *os_memcpy(VOID *dst, const VOID *src, UINT32 len);
+VOID *os_memmove(VOID *dest, const void *src,UINT32 len);
+VOID *os_memset(VOID *s, int c, size_t n);
+VOID *os_kzalloc(size_t size, unsigned int flags);
+
+void LD_load_code_from_bin(unsigned char **image, char *bin_name, char *path, mtkbt_dev_t *dev,u32 *code_len);
+
+int do_setMtkBT(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);
+int do_getMtkBTWakeT(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);
+
+#endif
diff --git a/uboot_driver/Mstar/include/mtk-bt/errno.h b/uboot_driver/Mstar/include/mtk-bt/errno.h
new file mode 100644
index 0000000..dab8156
--- /dev/null
+++ b/uboot_driver/Mstar/include/mtk-bt/errno.h
@@ -0,0 +1,208 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/******************************************************************************
+ *
+ * This file is provided under a dual license. When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+/*
+ * U-boot - errno.h Error number defines
+ *
+ * Copyright (c) 2005-2007 Analog Devices Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#ifndef _GENERIC_ERRNO_H
+#define _GENERIC_ERRNO_H
+
+#define EPERM 1 /* Operation not permitted */
+#define ENOENT 2 /* No such file or directory */
+#define ESRCH 3 /* No such process */
+#define EINTR 4 /* Interrupted system call */
+#define EIO 5 /* I/O error */
+#define ENXIO 6 /* No such device or address */
+#define E2BIG 7 /* Argument list too long */
+#define ENOEXEC 8 /* Exec format error */
+#define EBADF 9 /* Bad file number */
+#define ECHILD 10 /* No child processes */
+#define EAGAIN 11 /* Try again */
+#define ENOMEM 12 /* Out of memory */
+#define EACCES 13 /* Permission denied */
+#define EFAULT 14 /* Bad address */
+#define ENOTBLK 15 /* Block device required */
+#define EBUSY 16 /* Device or resource busy */
+#define EEXIST 17 /* File exists */
+#define EXDEV 18 /* Cross-device link */
+#define ENODEV 19 /* No such device */
+#define ENOTDIR 20 /* Not a directory */
+#define EISDIR 21 /* Is a directory */
+#define EINVAL 22 /* Invalid argument */
+#define ENFILE 23 /* File table overflow */
+#define EMFILE 24 /* Too many open files */
+#define ENOTTY 25 /* Not a typewriter */
+#define ETXTBSY 26 /* Text file busy */
+#define EFBIG 27 /* File too large */
+#define ENOSPC 28 /* No space left on device */
+#define ESPIPE 29 /* Illegal seek */
+#define EROFS 30 /* Read-only file system */
+#define EMLINK 31 /* Too many links */
+#define EPIPE 32 /* Broken pipe */
+#define EDOM 33 /* Math argument out of domain of func */
+#define ERANGE 34 /* Math result not representable */
+#define EDEADLK 35 /* Resource deadlock would occur */
+#define ENAMETOOLONG 36 /* File name too long */
+#define ENOLCK 37 /* No record locks available */
+#define ENOSYS 38 /* Function not implemented */
+#define ENOTEMPTY 39 /* Directory not empty */
+#define ELOOP 40 /* Too many symbolic links encountered */
+#define EWOULDBLOCK EAGAIN /* Operation would block */
+#define ENOMSG 42 /* No message of desired type */
+#define EIDRM 43 /* Identifier removed */
+#define ECHRNG 44 /* Channel number out of range */
+#define EL2NSYNC 45 /* Level 2 not synchronized */
+#define EL3HLT 46 /* Level 3 halted */
+#define EL3RST 47 /* Level 3 reset */
+#define ELNRNG 48 /* Link number out of range */
+#define EUNATCH 49 /* Protocol driver not attached */
+#define ENOCSI 50 /* No CSI structure available */
+#define EL2HLT 51 /* Level 2 halted */
+#define EBADE 52 /* Invalid exchange */
+#define EBADR 53 /* Invalid request descriptor */
+#define EXFULL 54 /* Exchange full */
+#define ENOANO 55 /* No anode */
+#define EBADRQC 56 /* Invalid request code */
+#define EBADSLT 57 /* Invalid slot */
+
+#define EDEADLOCK EDEADLK
+
+#define EBFONT 59 /* Bad font file format */
+#define ENOSTR 60 /* Device not a stream */
+#define ENODATA 61 /* No data available */
+#define ETIME 62 /* Timer expired */
+#define ENOSR 63 /* Out of streams resources */
+#define ENONET 64 /* Machine is not on the network */
+#define ENOPKG 65 /* Package not installed */
+#define EREMOTE 66 /* Object is remote */
+#define ENOLINK 67 /* Link has been severed */
+#define EADV 68 /* Advertise error */
+#define ESRMNT 69 /* Srmount error */
+#define ECOMM 70 /* Communication error on send */
+#define EPROTO 71 /* Protocol error */
+#define EMULTIHOP 72 /* Multihop attempted */
+#define EDOTDOT 73 /* RFS specific error */
+#define EBADMSG 74 /* Not a data message */
+#define EOVERFLOW 75 /* Value too large for defined data type */
+#define ENOTUNIQ 76 /* Name not unique on network */
+#define EBADFD 77 /* File descriptor in bad state */
+#define EREMCHG 78 /* Remote address changed */
+#define ELIBACC 79 /* Can not access a needed shared library */
+#define ELIBBAD 80 /* Accessing a corrupted shared library */
+#define ELIBSCN 81 /* .lib section in a.out corrupted */
+#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
+#define ELIBEXEC 83 /* Cannot exec a shared library directly */
+#define EILSEQ 84 /* Illegal byte sequence */
+#define ERESTART 85 /* Interrupted system call should be restarted */
+#define ESTRPIPE 86 /* Streams pipe error */
+#define EUSERS 87 /* Too many users */
+#define ENOTSOCK 88 /* Socket operation on non-socket */
+#define EDESTADDRREQ 89 /* Destination address required */
+#define EMSGSIZE 90 /* Message too long */
+#define EPROTOTYPE 91 /* Protocol wrong type for socket */
+#define ENOPROTOOPT 92 /* Protocol not available */
+#define EPROTONOSUPPORT 93 /* Protocol not supported */
+#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
+#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
+#define EPFNOSUPPORT 96 /* Protocol family not supported */
+#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
+#define EADDRINUSE 98 /* Address already in use */
+#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
+#define ENETDOWN 100 /* Network is down */
+#define ENETUNREACH 101 /* Network is unreachable */
+#define ENETRESET 102 /* Network dropped connection because of reset */
+#define ECONNABORTED 103 /* Software caused connection abort */
+#define ECONNRESET 104 /* Connection reset by peer */
+#define ENOBUFS 105 /* No buffer space available */
+#define EISCONN 106 /* Transport endpoint is already connected */
+#define ENOTCONN 107 /* Transport endpoint is not connected */
+#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
+#define ETOOMANYREFS 109 /* Too many references: cannot splice */
+#define ETIMEDOUT 110 /* Connection timed out */
+#define ECONNREFUSED 111 /* Connection refused */
+#define EHOSTDOWN 112 /* Host is down */
+#define EHOSTUNREACH 113 /* No route to host */
+#define EALREADY 114 /* Operation already in progress */
+#define EINPROGRESS 115 /* Operation now in progress */
+#define ESTALE 116 /* Stale NFS file handle */
+#define EUCLEAN 117 /* Structure needs cleaning */
+#define ENOTNAM 118 /* Not a XENIX named type file */
+#define ENAVAIL 119 /* No XENIX semaphores available */
+#define EISNAM 120 /* Is a named type file */
+#define EREMOTEIO 121 /* Remote I/O error */
+#define EDQUOT 122 /* Quota exceeded */
+#define ENOMEDIUM 123 /* No medium found */
+#define EMEDIUMTYPE 124 /* Wrong medium type */
+
+#endif
diff --git a/uboot_driver/Mstar/src/mtk-bt/LD_btmtk_usb.c b/uboot_driver/Mstar/src/mtk-bt/LD_btmtk_usb.c
new file mode 100644
index 0000000..5b951ad
--- /dev/null
+++ b/uboot_driver/Mstar/src/mtk-bt/LD_btmtk_usb.c
@@ -0,0 +1,2638 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/******************************************************************************
+ *
+ * This file is provided under a dual license. When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+/*
+* Copyright (c) 2014 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.
+*/
+
+//---------------------------------------------------------------------------
+#include <mtk-bt/LD_usbbt.h>
+#include <mtk-bt/LD_btmtk_usb.h>
+#include <mtk-bt/errno.h>
+
+/*============================================================================*/
+/* Local Configuration */
+/*============================================================================*/
+
+#define LD_VERSION "4.0.22010701"
+
+#define BUFFER_SIZE (1024 * 4) /* Size of RX Queue */
+#define BT_SEND_HCI_CMD_BEFORE_SUSPEND 1
+#define LD_SUPPORT_FW_DUMP 0
+#define LD_BT_ALLOC_BUF 0
+#define LD_NOT_FIX_BUILD_WARN 0
+
+#define FIDX 0x5A /* Unify WoBLE APCF Filtering Index */
+#define BUZZARD_FIDX 0x0A /* Unify WoBLE APCF Filtering Index */
+#define FIDX_OFFSET_RADIO_OFF 19 /* Unify WoBLE APCF Filtering Index offset in radio_off cmd*/
+#define FIDX_OFFSET_APCF 5 /* Unify WoBLE APCF Filtering Index offset in apcf related cmd*/
+
+#define WOBLE_LOG_OFFSET_76XX 37
+#define WOBLE_LOG_VALUE_76XX 0x31
+
+/*============================================================================*/
+/* Global Variable */
+/*============================================================================*/
+static char driver_version[64] = { 0 };
+static unsigned char probe_counter = 0;
+static volatile int metaMode;
+static volatile int metaCount;
+/* 0: False; 1: True */
+static int isbtready;
+static int isUsbDisconnet;
+static volatile int is_assert = 0;
+static u8 u8WoBTW = PM_SOURCE_DISABLE;
+
+/*============================================================================*/
+/* Extern Functions */
+/*============================================================================*/
+extern int snprintf(char *str, size_t size, const char *fmt, ...);
+
+static inline int is_mt7630(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76300000);
+}
+
+static inline int is_mt7650(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76500000);
+}
+
+static inline int is_mt7632(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76320000);
+}
+
+static inline int is_mt7662(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff0000) == 0x76620000);
+}
+
+static inline int is_mt7662T(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffffffff) == 0x76620100);
+}
+
+static inline int is_mt7632T(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffffffff) == 0x76320100);
+}
+
+static inline int is_mt7668(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7668);
+}
+
+static inline int is_mt7663(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7663);
+}
+
+static inline int is_mt7961(struct LD_btmtk_usb_data *data)
+{
+ return ((data->chip_id & 0xffff) == 0x7961);
+}
+
+static inline int is_support_unify_woble(struct LD_btmtk_usb_data *data)
+{
+ if (data->bt_cfg.support_unify_woble) {
+ if (is_mt7668(data) || is_mt7663(data) || is_mt7961(data))
+ return 1;
+ else
+ return 0;
+ } else {
+ return 0;
+ }
+}
+
+/*============================================================================*/
+/* Internal Functions */
+/*============================================================================*/
+static int btmtk_usb_io_read32(struct LD_btmtk_usb_data *data, u32 reg, u32 *val)
+{
+ u8 request = data->r_request;
+ int ret;
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, request,
+ DEVICE_VENDOR_REQUEST_IN, 0, (u16)reg, data->io_buf, sizeof(u32),
+ CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ *val = 0xffffffff;
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, *val);
+ return ret;
+ }
+
+ os_memmove(val, data->io_buf, sizeof(u32));
+ *val = le32_to_cpu(*val);
+ return 0;
+}
+
+static int btmtk_usb_io_read32_7xxx(struct LD_btmtk_usb_data *data, u32 reg, u32 *val)
+{
+ int ret = -1;
+ __le16 reg_high;
+ __le16 reg_low;
+
+ reg_high = ((reg >> 16) & 0xFFFF);
+ reg_low = (reg & 0xFFFF);
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x63,
+ DEVICE_VENDOR_REQUEST_IN, reg_high, reg_low, data->io_buf, sizeof(u32),
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ *val = 0xFFFFFFFF;
+ usb_debug("error(%d), reg=%X, value=%X\n", ret, reg, *val);
+ return ret;
+ }
+
+ os_memmove(val, data->io_buf, sizeof(u32));
+ *val = le32_to_cpu(*val);
+ return 0;
+}
+
+static int btmtk_usb_io_write32(struct LD_btmtk_usb_data *data, u32 reg, u32 val)
+{
+ u16 value, index;
+ u8 request = data->w_request;
+ mtkbt_dev_t *udev = data->udev;
+ int ret;
+
+ index = (u16) reg;
+ value = val & 0x0000ffff;
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP, request, DEVICE_VENDOR_REQUEST_OUT,
+ value, index, NULL, 0, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, val);
+ return ret;
+ }
+
+ index = (u16) (reg + 2);
+ value = (val & 0xffff0000) >> 16;
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP, request, DEVICE_VENDOR_REQUEST_OUT,
+ value, index, NULL, 0, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d), reg=%x, value=%x\n", ret, reg, val);
+ return ret;
+ }
+ if (ret > 0)
+ {
+ ret = 0;
+ }
+ return ret;
+}
+
+static int btmtk_usb_send_wmt_cmd(struct LD_btmtk_usb_data *data, const u8 *cmd,
+ const int cmd_len, const u8 *event, const int event_len, u32 delay, u8 retry)
+{
+ int ret = -1;
+ BOOL check = FALSE;
+
+ if (!data || !data->hcif || !data->io_buf || !cmd) {
+ usb_debug("incorrect cmd pointer\n");
+ return -1;
+ }
+ if (event != NULL && event_len > 0)
+ check = TRUE;
+
+ /* send WMT command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0x01,
+ DEVICE_CLASS_REQUEST_OUT, 0x30, 0x00, (void *)cmd, cmd_len,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ if (event_len == -1) {
+ /* If event_len is -1, DO NOT read event, since FW wouldn't feedback */
+ return 0;
+ }
+
+retry_get:
+ MTK_MDELAY(delay);
+
+ /* check WMT event */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x01,
+ DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf, LD_BT_MAX_EVENT_SIZE,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("event get failed(%d)\n", ret);
+ if (check == TRUE)
+ return ret;
+ else
+ return 0;
+ }
+
+ if (check == TRUE) {
+ if (ret >= event_len && memcmp(event, data->io_buf, event_len) == 0) {
+ return ret;
+ } else if (retry > 0) {
+ usb_debug("retry to get event(%d)\n", retry);
+ retry--;
+ goto retry_get;
+ } else {
+ usb_debug("can't get expect event\n");
+ usb_debug_raw(event, event_len, "EXPECT:");
+ usb_debug_raw(data->io_buf, ret, "RCV:");
+ }
+ } else {
+ if (ret > 0) {
+ usb_debug_raw(cmd, cmd_len, "CMD:");
+ usb_debug_raw(data->io_buf, ret, "EVT:");
+ return 0;
+ } else if (retry > 0) {
+ usb_debug("retry to get event(%d)\n", retry);
+ retry--;
+ goto retry_get;
+ } else {
+ usb_debug("can't get expect event\n");
+ }
+ }
+ return -1;
+}
+
+static int btmtk_usb_send_hci_cmd(struct LD_btmtk_usb_data *data, u8 *cmd,
+ const int cmd_len, const u8 *event, const int event_len)
+{
+ /** @RETURN
+ * length if event compare successfully.,
+ * 0 if doesn't check event.,
+ * < 0 if error.
+ */
+#define USB_CTRL_IO_TIMO 100
+#define USB_INTR_MSG_TIMO 2000
+ int ret = -1;
+ int len = 0;
+ int i = 0;
+ u8 retry = 0;
+ BOOL check = FALSE;
+
+ if (!data || !data->hcif || !data->io_buf || !cmd) {
+ usb_debug("incorrect cmd pointer\n");
+ return -1;
+ }
+ if (event != NULL && event_len > 0)
+ check = TRUE;
+
+ /* send HCI command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0,
+ DEVICE_CLASS_REQUEST_OUT, 0, 0, (u8 *)cmd, cmd_len, USB_CTRL_IO_TIMO);
+ if (ret < 0) {
+ usb_debug("send command failed: %d\n", ret);
+ return ret;
+ }
+
+ if (event_len == -1) {
+ /* If event_len is -1, DO NOT read event, since FW wouldn't feedback */
+ return 0;
+ }
+
+ /* check HCI event */
+ do {
+ memset(data->io_buf, 0, LD_BT_MAX_EVENT_SIZE);
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, USB_INTR_MSG_TIMO);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ if (check == TRUE) return ret;
+ else return 0;
+ }
+
+ if (check == TRUE) {
+ if (len >= event_len) {
+ for (i = 0; i < event_len; i++) {
+ if (event[i] != data->io_buf[i])
+ break;
+ }
+ } else {
+ usb_debug("event length is not match(%d/%d)\n", len, event_len);
+ }
+ if (i != event_len) {
+ usb_debug("got unknown event(%d)\n", len);
+ } else {
+ return len; /* actually read length */
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ }
+ usb_debug("try get event again\n");
+ } while (retry < 3);
+ return -1;
+}
+
+static int btmtk_usb_send_hci_suspend_cmd(struct LD_btmtk_usb_data *data)
+{
+ int ret = -1;
+#if SUPPORT_HISENSE_WoBLE
+ u8 cmd[] = {0xC9, 0xFC, 0x02, 0x01, 0x0D}; // for Hisense WoBLE
+
+ usb_debug("issue wake up command for Hisense\n");
+#else
+ u8 cmd[] = {0xC9, 0xFC, 0x0D, 0x01, 0x0E, 0x00, 0x05, 0x43,
+ 0x52, 0x4B, 0x54, 0x4D, 0x20, 0x04, 0x32, 0x00};
+
+ usb_debug("issue wake up command for '0E: MTK WoBLE Ver2'\n");
+#endif
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), NULL, -1);
+ if (ret < 0) {
+ usb_debug("error(%d)\n", ret);
+ return ret;
+ }
+ usb_debug("send suspend cmd OK\n");
+ return 0;
+}
+
+static int btmtk_usb_send_hci_reset_cmd(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x03, 0x0C, 0x00 };
+ u8 event[] = { 0x0E, 0x04, 0x01, 0x03, 0x0C, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_hci_set_ce_cmd(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0xD1, 0xFC, 0x04, 0x0C, 0x07, 0x41, 0x00 };
+ u8 event[] = { 0x0E, 0x08, 0x01, 0xD1, 0xFC, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+
+ } else if (ret == sizeof(event) + 4) {
+ if (data->io_buf[6] & 0x01) {
+ usb_debug("warning, 0x41070c[0] is 1!\n");
+ ret = 0;
+ } else {
+ u8 cmd2[11] = { 0xD0, 0xFC, 0x08, 0x0C, 0x07, 0x41, 0x00 };
+
+ cmd2[7] = data->io_buf[6] | 0x01;
+ cmd2[8] = data->io_buf[7];
+ cmd2[9] = data->io_buf[8];
+ cmd2[10] = data->io_buf[9];
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd2, sizeof(cmd2), NULL, 0);
+ if (ret < 0) {
+ usb_debug("write 0x41070C failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ ret = 0;
+ }
+ }
+ } else {
+ usb_debug("failed, incorrect response length(%d)\n", ret);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_check_rom_patch_result_cmd(struct LD_btmtk_usb_data *data)
+{
+ /* Send HCI Reset */
+ {
+ int ret = 0;
+ unsigned char buf[8] = { 0 };
+ buf[0] = 0xD1;
+ buf[1] = 0xFC;
+ buf[2] = 0x04;
+ buf[3] = 0x00;
+ buf[4] = 0xE2;
+ buf[5] = 0x40;
+ buf[6] = 0x00;
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP,0x0, DEVICE_CLASS_REQUEST_OUT,
+ 0x00, 0x00, buf, 0x07, 100);
+ if (ret < 0)
+ {
+ usb_debug("error1(%d)\n", ret);
+ return ret;
+ }
+ }
+ /* Get response of HCI reset */
+ {
+ int ret = 0;
+ unsigned char buf[LD_BT_MAX_EVENT_SIZE] = { 0 };
+ int actual_length = 0;
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, buf, LD_BT_MAX_EVENT_SIZE,
+ &actual_length, USB_INTR_MSG_TIMO);
+ if (ret < 0)
+ {
+ usb_debug("error2(%d)\n", ret);
+ return ret;
+ }
+ usb_debug("Check rom patch result : ");
+
+ if (buf[6] == 0 && buf[7] == 0 && buf[8] == 0 && buf[9] == 0)
+ {
+ usb_debug("NG\n");
+ }
+ else
+ {
+ usb_debug("OK\n");
+ }
+ }
+ return 0;
+}
+
+static int btmtk_usb_switch_iobase(struct LD_btmtk_usb_data *data, int base)
+{
+ int ret = 0;
+
+ switch (base)
+ {
+ case SYSCTL:
+ data->w_request = 0x42;
+ data->r_request = 0x47;
+ break;
+ case WLAN:
+ data->w_request = 0x02;
+ data->r_request = 0x07;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static void btmtk_usb_cap_init(struct LD_btmtk_usb_data *data)
+{
+ unsigned char *str_end ;
+
+ usb_debug("chip id = %x\n", data->chip_id);
+
+ if (data->chip_id == 0)
+ btmtk_usb_io_read32(data, 0x00, &data->chip_id);
+ if (data->chip_id == 0)
+ btmtk_usb_io_read32_7xxx(data, 0x80000008, &data->chip_id);
+
+ if (is_mt7630(data) || is_mt7650(data)) {
+ data->need_load_fw = 1;
+ data->need_load_rom_patch = 0;
+ data->fw_header_image = NULL;
+ data->fw_bin_file_name = (unsigned char*)strdup("mtk/mt7650.bin");
+ data->fw_len = 0;
+
+ } else if (is_mt7662T(data) || is_mt7632T(data)) {
+ usb_debug("btmtk:This is 7662T chip\n");
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ os_memcpy(data->rom_patch_bin_file_name, "mt7662t_patch_e1_hdr.bin", 24);
+ data->rom_patch_offset = 0xBC000;
+ data->rom_patch_len = 0;
+
+ } else if (is_mt7632(data) || is_mt7662(data)) {
+ usb_debug("btmtk:This is 7662 chip\n");
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ os_memcpy(data->rom_patch_bin_file_name, "mt7662_patch_e3_hdr.bin", 23);
+ data->rom_patch_offset = 0x90000;
+ data->rom_patch_len = 0;
+
+ } else if (is_mt7668(data) || is_mt7663(data)) {
+ unsigned int fw_ver = 0;
+
+ btmtk_usb_io_read32_7xxx(data, 0x80000004, &fw_ver);
+ if ((fw_ver & 0xFF) != 0xFF) {
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+
+ /* Bin filename format : "mt$$$$_patch_e%.bin"
+ * $$$$ : chip id
+ * % : fw version & 0xFF + 1 (in HEX)
+ */
+ (void)snprintf((char *)data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN, "mt%04x_patch_e%x_hdr.bin",
+ data->chip_id & 0xFFFF, (fw_ver & 0xFF) + 1);
+ usb_debug("patch name: %s\n", data->rom_patch_bin_file_name);
+ data->rom_patch_len = 0;
+ } else {
+ usb_debug("Incorrect firmware version: 0xFF");
+ return;
+ }
+
+ if (is_mt7668(data)) {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME_7668,
+ sizeof(WOBLE_SETTING_FILE_NAME_7668));
+ usb_debug("woble setting file name is %s\n", WOBLE_SETTING_FILE_NAME_7668);
+ } else if (is_mt7663(data)) {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME_7663,
+ sizeof(WOBLE_SETTING_FILE_NAME_7663));
+ usb_debug("woble setting file name is %s\n", WOBLE_SETTING_FILE_NAME_7663);
+ } else {
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME,
+ sizeof(WOBLE_SETTING_FILE_NAME));
+ usb_debug("woble setting file name is %s\n", WOBLE_SETTING_FILE_NAME);
+ }
+ } else {
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_CHIP_ID, &data->chip_id);
+ if (is_mt7961(data)) {
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_FLAVOR, &data->flavor);
+ btmtk_usb_io_read32_7xxx(data, BUZZARD_FW_VERSION, &data->fw_version);
+ } else {
+ usb_debug("Unknown Mediatek device(%04X)\n", data->chip_id);
+ return;
+ }
+
+ usb_debug("Chip ID = 0x%x\n", data->chip_id);
+ usb_debug("flavor = 0x%x\n", data->flavor);
+ usb_debug("FW Ver = 0x%x\n", data->fw_version);
+
+ memset(data->rom_patch_bin_file_name, 0, MAX_BIN_FILE_NAME_LEN);
+ if ((data->fw_version & 0xff) == 0xff) {
+ usb_debug("Wrong FW version : 0x%x !", data->fw_version);
+ return;
+ }
+
+ data->need_load_fw = 0;
+ data->need_load_rom_patch = 1;
+ /* Bin filename format : "BT_RAM_CODE_MT%04x_%x_%x_hdr.bin"
+ * $$$$ : chip id
+ * % : fw version & 0xFF + 1 (in HEX)
+ */
+ data->flavor = (data->flavor & 0x00000080) >> 7;
+ /* if flavor equals 1, it represent 7920, else it represent 7921 */
+ if (data->flavor)
+ (void)snprintf((char *)data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "BT_RAM_CODE_MT%04x_1a_%x_hdr.bin", data->chip_id & 0xffff,
+ (data->fw_version & 0xff) + 1);
+ else
+ (void)snprintf(data->rom_patch_bin_file_name, MAX_BIN_FILE_NAME_LEN,
+ "BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
+ data->chip_id & 0xffff, (data->fw_version & 0xff) + 1);
+
+ if(strlen((char *)data->rom_patch_bin_file_name) > MAX_BIN_FILE_NAME_LEN){
+ str_end = data->rom_patch_bin_file_name + MAX_BIN_FILE_NAME_LEN;
+ *str_end='\0';
+ }
+ usb_debug("patch name: %s\n", data->rom_patch_bin_file_name);
+ data->rom_patch_len = 0;
+
+ memcpy(data->woble_setting_file_name, WOBLE_SETTING_FILE_NAME_7961,
+ sizeof(WOBLE_SETTING_FILE_NAME_7961));
+ usb_debug("woble setting file name is %s", WOBLE_SETTING_FILE_NAME_7961);
+ }
+}
+
+#if CRC_CHECK
+static u16 checksume16(u8 *pData, int len)
+{
+ int sum = 0;
+
+ while (len > 1)
+ {
+ sum += *((u16 *) pData);
+
+ pData = pData + 2;
+
+ if (sum & 0x80000000)
+ {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+ len -= 2;
+ }
+
+ if (len)
+ sum += *((u8 *) pData);
+
+ while (sum >> 16)
+ {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+
+ return ~sum;
+}
+
+static int btmtk_usb_chk_crc(struct LD_btmtk_usb_data *data, u32 checksum_len)
+{
+ int ret = 0;
+ mtkbt_dev_t *udev = data->udev;
+
+ usb_debug("\n");
+
+ os_memmove(data->io_buf, &data->rom_patch_offset, 4);
+ os_memmove(&data->io_buf[4], &checksum_len, 4);
+
+ ret = data->hcif->usb_control_msg(udev, MTKBT_CTRL_TX_EP,0x1, DEVICE_VENDOR_REQUEST_OUT,
+ 0x20, 0x00, data->io_buf, 8, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d)\n", ret);
+ }
+
+ return ret;
+}
+
+static u16 btmtk_usb_get_crc(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ mtkbt_dev_t *udev = data->udev;
+ u16 crc, count = 0;
+
+ usb_debug("\n");
+
+ while (1)
+ {
+ ret =
+ data->hcif->usb_control_msg(udev, MTKBT_CTRL_RX_EP, 0x01, DEVICE_VENDOR_REQUEST_IN,
+ 0x21, 0x00, data->io_buf, 2, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ crc = 0xFFFF;
+ usb_debug("error(%d)\n", ret);
+ }
+
+ os_memmove(&crc, data->io_buf, 2);
+
+ crc = le16_to_cpu(crc);
+
+ if (crc != 0xFFFF)
+ break;
+
+ MTK_MDELAY(100);
+
+ if (count++ > 100)
+ {
+ usb_debug("Query CRC over %d times\n", count);
+ break;
+ }
+ }
+
+ return crc;
+}
+#endif /* CRC_CHECK */
+
+static int btmtk_usb_send_wmt_reset_cmd(struct LD_btmtk_usb_data *data)
+{
+ /* reset command */
+ u8 cmd[] = { 0x6F, 0xFC, 0x05, 0x01, 0x07, 0x01, 0x00, 0x04 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x07, 0x01, 0x00, 0x00 };
+ int ret = -1;
+
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 20, 0);
+ if (ret < 0) {
+ usb_debug("Check reset wmt result : NG\n");
+ } else {
+ usb_debug("Check reset wmt result : OK\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_wmt_cfg(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ int index = 0;
+
+ usb_debug("send wmt cmd!\n");
+
+ for (index = 0; index < WMT_CMD_COUNT; index++) {
+ if (data->bt_cfg.wmt_cmd[index].content && data->bt_cfg.wmt_cmd[index].length) {
+ ret = btmtk_usb_send_wmt_cmd(data, data->bt_cfg.wmt_cmd[index].content,
+ data->bt_cfg.wmt_cmd[index].length, NULL, 0, 100, 10);
+ if (ret < 0) {
+ usb_debug("Send wmt cmd failed(%d)! Index: %d\n", ret, index);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static u16 btmtk_usb_get_rom_patch_result(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP, 0x01,
+ DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf, 7,
+ CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0)
+ {
+ usb_debug("error(%d)\n", ret);
+ }
+
+ if (data->io_buf[0] == 0xe4 &&
+ data->io_buf[1] == 0x05 &&
+ data->io_buf[2] == 0x02 &&
+ data->io_buf[3] == 0x01 &&
+ data->io_buf[4] == 0x01 &&
+ data->io_buf[5] == 0x00 &&
+ data->io_buf[6] == 0x00)
+ {
+ //usb_debug("Get rom patch result : OK\n");
+ }
+ else
+ {
+ usb_debug("Get rom patch result : NG\n");
+ }
+ return ret;
+}
+
+/**
+ * Only for load rom patch function, tmp_str[15] is '\n'
+ */
+#define SHOW_FW_DETAILS(s) \
+ usb_debug("%s = %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", s, \
+ tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3], \
+ tmp_str[4], tmp_str[5], tmp_str[6], tmp_str[7], \
+ tmp_str[8], tmp_str[9], tmp_str[10], tmp_str[11], \
+ tmp_str[12], tmp_str[13], tmp_str[14], tmp_str[15])
+
+static int btmtk_usb_load_rom_patch(struct LD_btmtk_usb_data *data)
+{
+ u32 loop = 0;
+ u32 value;
+ s32 sent_len;
+ int ret = 0;
+ u32 patch_len = 0;
+ u32 cur_len = 0;
+ int real_len = 0;
+ int first_block = 1;
+ unsigned char phase;
+ void *buf;
+ char *pos;
+ unsigned char *tmp_str;
+
+ //usb_debug("begin\n");
+load_patch_protect:
+ btmtk_usb_switch_iobase(data, WLAN);
+ btmtk_usb_io_read32(data, SEMAPHORE_03, &value);
+ loop++;
+
+ if ((value & 0x01) == 0x00)
+ {
+ if (loop < 1000)
+ {
+ MTK_MDELAY(1);
+ goto load_patch_protect;
+ }
+ else
+ {
+ usb_debug("btmtk_usb_load_rom_patch ERR! Can't get semaphore! Continue\n");
+ }
+ }
+
+ btmtk_usb_switch_iobase(data, SYSCTL);
+
+ btmtk_usb_io_write32(data, 0x1c, 0x30);
+
+ btmtk_usb_switch_iobase(data, WLAN);
+
+ /* check ROM patch if upgrade */
+ if ((MT_REV_GTE(data, mt7662, REV_MT76x2E3)) || (MT_REV_GTE(data, mt7632, REV_MT76x2E3)))
+ {
+ btmtk_usb_io_read32(data, CLOCK_CTL, &value);
+ if ((value & 0x01) == 0x01)
+ {
+ usb_debug("btmtk_usb_load_rom_patch : no need to load rom patch\n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ goto error;
+ }
+ }
+ else
+ {
+ btmtk_usb_io_read32(data, COM_REG0, &value);
+ if ((value & 0x02) == 0x02)
+ {
+ usb_debug("btmtk_usb_load_rom_patch : no need to load rom patch\n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ goto error;
+ }
+ }
+
+ buf = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!buf)
+ {
+ ret = -ENOMEM;
+ goto error;
+ }
+ pos = buf;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name, NULL,
+ data->udev, &data->rom_patch_len);
+
+ if (!data->rom_patch)
+ {
+ usb_debug("please assign a rom patch(/vendor/firmware/%s)or(/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name,
+ data->rom_patch_bin_file_name);
+ ret = -1;
+ goto error1;
+ }
+
+ tmp_str = data->rom_patch;
+ SHOW_FW_DETAILS("FW Version");
+ SHOW_FW_DETAILS("build Time");
+
+ tmp_str = data->rom_patch + 16;
+ usb_debug("platform = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 20;
+ usb_debug("HW/SW version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 24;
+ usb_debug("Patch version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ usb_debug("\nloading rom patch...\n");
+
+ cur_len = 0x00;
+ patch_len = data->rom_patch_len - PATCH_INFO_SIZE;
+
+ /* loading rom patch */
+ while (1)
+ {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE_BULK_EP;
+ real_len = 0;
+ sent_len =
+ (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+
+ //usb_debug("patch_len = %d\n", patch_len);
+ //usb_debug("cur_len = %d\n", cur_len);
+ //usb_debug("sent_len = %d\n", sent_len);
+
+ if (sent_len > 0)
+ {
+ if (first_block == 1)
+ {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ }
+ else if (sent_len == sent_len_max)
+ {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ }
+ else
+ {
+ phase = PATCH_PHASE3;
+ }
+
+ /* prepare HCI header */
+ pos[0] = 0x6F;
+ pos[1] = 0xFC;
+ pos[2] = (sent_len + 5) & 0xFF;
+ pos[3] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ pos[4] = 0x01;
+ pos[5] = 0x01;
+ pos[6] = (sent_len + 1) & 0xFF;
+ pos[7] = ((sent_len + 1) >> 8) & 0xFF;
+
+ pos[8] = phase;
+
+ os_memcpy(&pos[9], data->rom_patch + PATCH_INFO_SIZE + cur_len, sent_len);
+
+ //usb_debug("sent_len + PATCH_HEADER_SIZE = %d, phase = %d\n",
+ //sent_len + PATCH_HEADER_SIZE, phase);
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, buf, sent_len + PATCH_HEADER_SIZE_BULK_EP, &real_len, 0);
+
+ if (ret)
+ {
+ usb_debug("upload rom_patch err: %d\n", ret);
+ goto error1;
+ }
+
+ MTK_MDELAY(1);
+
+ cur_len += sent_len;
+
+ }
+ else
+ {
+ usb_debug("loading rom patch... Done\n");
+ break;
+ }
+ }
+
+ MTK_MDELAY(20);
+ ret = btmtk_usb_get_rom_patch_result(data);
+ MTK_MDELAY(20);
+
+ /* Send Checksum request */
+ #if CRC_CHECK
+ int total_checksum = checksume16(data->rom_patch + PATCH_INFO_SIZE, patch_len);
+ btmtk_usb_chk_crc(data, patch_len);
+ MTK_MDELAY(20);
+ if (total_checksum != btmtk_usb_get_crc(data))
+ {
+ usb_debug("checksum fail!, local(0x%x) <> fw(0x%x)\n", total_checksum,
+ btmtk_usb_get_crc(data));
+ ret = -1;
+ goto error1;
+ }
+ else
+ {
+ usb_debug("crc match!\n");
+ }
+ #endif
+ MTK_MDELAY(20);
+ /* send check rom patch result request */
+ btmtk_usb_send_check_rom_patch_result_cmd(data);
+ MTK_MDELAY(20);
+ /* CHIP_RESET */
+ ret = btmtk_usb_send_wmt_reset_cmd(data);
+ MTK_MDELAY(20);
+ /* BT_RESET */
+ btmtk_usb_send_hci_reset_cmd(data);
+ /* for WoBLE/WoW low power */
+ btmtk_usb_send_hci_set_ce_cmd(data);
+
+error1:
+ os_kfree(buf);
+
+error:
+ btmtk_usb_io_write32(data, SEMAPHORE_03, 0x1);
+ //usb_debug("end\n");
+ return ret;
+}
+
+static int btmtk_usb_send_wmt_power_on_cmd_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 count = 0; /* retry 3 times */
+ u8 cmd[] = { 0x6F, 0xFC, 0x06, 0x01, 0x06, 0x02, 0x00, 0x00, 0x01 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x06, 0x01, 0x00 }; /* event[6] is key */
+ int ret = -1; /* if successful, 0 */
+
+ do {
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 100, 10);
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else if (ret == sizeof(event) + 1) {
+ switch (data->io_buf[6]) {
+ case 0: /* successful */
+ usb_debug("OK\n");
+ ret = 0;
+ break;
+ case 2: /* retry */
+ usb_debug("Try again\n");
+ continue;
+ default:
+ usb_debug("Unknown result: %02X\n", data->io_buf[6]);
+ return -1;
+ }
+ } else {
+ usb_debug("failed, incorrect response length(%d)\n", ret);
+ return -1;
+ }
+ } while (++count < 3 && ret > 0);
+
+ return ret;
+}
+
+static int btmtk_usb_send_hci_tci_set_sleep_cmd_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x7A, 0xFC, 0x07, 0x05, 0x40, 0x06, 0x40, 0x06, 0x00, 0x00 };
+ u8 event[] = { 0x0E, 0x04, 0x01, 0x7A, 0xFC, 0x00 };
+ int ret = -1; /* if successful, 0 */
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_get_vendor_cap(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x53, 0xFD, 0x00 };
+ u8 event[6] = { 0x0E, 0x12, 0x01, 0x53, 0xFD, 0x00, /* ... */ };
+ int ret = -1;
+
+ // TODO: should not compare whole event
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Failed(%d)\n", ret);
+ } else {
+ usb_debug("OK\n");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_apcf_reserved_79xx(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ u8 reserve_apcf_cmd[] = { 0xC9, 0xFC, 0x05, 0x01, 0x30, 0x02, 0x61, 0x02 };
+ u8 reserve_apcf_event[] = { 0xE6, 0x02, 0x08, 0x11 };
+
+ ret = btmtk_usb_send_hci_cmd(data, reserve_apcf_cmd, sizeof(reserve_apcf_cmd),
+ reserve_apcf_event, sizeof(reserve_apcf_event));
+ if (ret < 0 ) {
+ usb_debug("Failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int btmtk_usb_send_read_bdaddr(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x09, 0x10, 0x00 };
+ u8 event[] = { 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00, /* 6 bytes are BDADDR */ };
+ int ret = -1;
+
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0 || ret != 12 /* Event actual length */) {
+ usb_debug("Failed(%d)\n", ret);
+ return ret;
+ }
+
+ os_memcpy(data->local_addr, data->io_buf + 6, BD_ADDR_LEN);
+ usb_debug("ADDR: %02X-%02X-%02X-%02X-%02X-%02X\n",
+ data->local_addr[5], data->local_addr[4], data->local_addr[3],
+ data->local_addr[2], data->local_addr[1], data->local_addr[0]);
+ ret = 0;
+
+ return ret;
+}
+
+static int btmtk_usb_set_apcf(struct LD_btmtk_usb_data *data, BOOL bin_file)
+{
+ int i = 0, ret = -1;
+ // Legacy RC pattern
+ u8 manufacture_data[] = { 0x57, 0xFD, 0x27, 0x06, 0x00, FIDX,
+ 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x43, 0x52, 0x4B, 0x54, 0x4D, /* manufacturer data */
+ 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* mask */
+ u8 filter_cmd[] = { 0x57, 0xFD, 0x0A, 0x01, 0x00, FIDX, 0x20, 0x00,
+ 0x00, 0x00, 0x01, 0x80, 0x00 };
+ u8 event[] = { 0x0E, 0x07, 0x01, 0x57, 0xFD, 0x00, /* ... */ };
+
+ if (is_mt7961(data)) {
+ manufacture_data[FIDX_OFFSET_APCF] = BUZZARD_FIDX;
+ filter_cmd[FIDX_OFFSET_APCF] = BUZZARD_FIDX;
+ }
+
+ if (bin_file) {
+ if (data->wake_dev_len) {
+ /* wake_on_ble.conf using 90(0x5A-FIDX) as filter_index */
+ u8 pos = 0;
+ u8 broadcast_addr[] = { 0x57, 0xFD, 0x0A, 0x02, 0x00, FIDX,
+ 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, // ADDRESS
+ 0x00 }; // 0: Public, 1: Random
+ u8 adv_pattern[] = { 0x57, 0xFD, 0x15, 0x06, 0x00, FIDX,
+ 0x71, 0x01, // VID
+ 0x04, 0x11, // PID
+ 0x00, 0x00, 0x00, 0x00, // IR key code
+ 0x00, // sequence number
+ 0xFF, 0xFF, // mask~
+ 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00};
+
+ // BDADDR
+ for (i = 0; i < data->wake_dev[1]; i++) {
+ broadcast_addr[11] = data->wake_dev[2 + i * BD_ADDR_LEN + 0];
+ broadcast_addr[10] = data->wake_dev[2 + i * BD_ADDR_LEN + 1];
+ broadcast_addr[9] = data->wake_dev[2 + i * BD_ADDR_LEN + 2];
+ broadcast_addr[8] = data->wake_dev[2 + i * BD_ADDR_LEN + 3];
+ broadcast_addr[7] = data->wake_dev[2 + i * BD_ADDR_LEN + 4];
+ broadcast_addr[6] = data->wake_dev[2 + i * BD_ADDR_LEN + 5];
+ ret = btmtk_usb_send_hci_cmd(data, broadcast_addr, sizeof(broadcast_addr),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set broadcast address fail\n");
+ continue;
+ }
+ // mask broadcast address as a filter condition
+ filter_cmd[6] = 0x21;
+ }
+ usb_debug("There are %d broadcast address filter(s) from %s\n", i, WAKE_DEV_RECORD);
+
+ /** VID/PID in conf is LITTLE endian, but PID in ADV is BIG endian */
+ pos = 2 + data->wake_dev[1] * 6;
+ for (i = 0; i < data->wake_dev[pos]; i++) {
+ adv_pattern[6] = data->wake_dev[pos + (i * 4) + 1];
+ adv_pattern[7] = data->wake_dev[pos + (i * 4) + 2];
+ adv_pattern[9] = data->wake_dev[pos + (i * 4) + 3];
+ adv_pattern[8] = data->wake_dev[pos + (i * 4) + 4];
+ ret = btmtk_usb_send_hci_cmd(data, adv_pattern, sizeof(adv_pattern),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set advertising patten fail\n");
+ return ret;
+ }
+ }
+ usb_debug("There are %d manufacture data filter(s) from %s\n", i, WAKE_DEV_RECORD);
+
+ // Filtering parameters
+ ret = btmtk_usb_send_hci_cmd(data, filter_cmd, sizeof(filter_cmd),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set filtering parm fail\n");
+ return ret;
+ }
+
+ // if wake_on_ble.conf exist, no need use default woble_setting.bin
+ } else {
+ // woble_setting.bin
+ usb_debug("Set APCF filter from woble_setting.bin\n");
+ for (i = 0; i < WOBLE_SETTING_COUNT; i++) {
+ if (!data->woble_setting_apcf[i].length)
+ continue;
+
+ if ((data->woble_setting_apcf_fill_mac[i].content[0] == 1) &&
+ data->woble_setting_apcf_fill_mac_location[i].length) {
+ /* need add BD addr to apcf cmd */
+ memcpy(data->woble_setting_apcf[i].content +
+ (*data->woble_setting_apcf_fill_mac_location[i].content),
+ data->local_addr, BD_ADDR_LEN);
+ }
+
+ usb_debug_raw(data->woble_setting_apcf[i].content,
+ data->woble_setting_apcf[i].length,
+ "Send woble_setting_apcf[%d] ", i);
+
+ ret = btmtk_usb_send_hci_cmd(data, data->woble_setting_apcf[i].content,
+ data->woble_setting_apcf[i].length, event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set apcf_cmd[%d] data fail\n", i);
+ return ret;
+ }
+ }
+ }
+ } else {
+ // Use default
+ usb_debug("Using default APCF filter\n");
+ os_memcpy(manufacture_data + 9, data->local_addr, BD_ADDR_LEN);
+ ret = btmtk_usb_send_hci_cmd(data, manufacture_data,
+ sizeof(manufacture_data), event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set manufacture data fail\n");
+ return ret;
+ }
+
+ ret = btmtk_usb_send_hci_cmd(data, filter_cmd, sizeof(filter_cmd),
+ event, sizeof(event));
+ if (ret < 0) {
+ usb_debug("Set manufacture data fail\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int btmtk_usb_check_need_load_patch_7668(struct LD_btmtk_usb_data *data)
+{
+ u8 cmd[] = { 0x6F, 0xFC, 0x05, 0x01, 0x17, 0x01, 0x00, 0x01 };
+ u8 event[] = { 0xE4, 0x05, 0x02, 0x17, 0x01, 0x00, /* 0x02 */ }; /* event[6] is key */
+ int ret = -1;
+
+ ret = btmtk_usb_send_wmt_cmd(data, cmd, sizeof(cmd), event, sizeof(event), 20, 0);
+ /* can't get correct event */
+ if (ret < 0) {
+ usb_debug("check need load patch or not fail(%d)\n", ret);
+ return PATCH_ERR;
+ }
+
+ if (ret >= sizeof(event) + 1) {
+ usb_debug("%s: return len is %d\n", __func__, ret);
+ return data->io_buf[6];
+ }
+
+ return PATCH_ERR;
+}
+
+static int btmtk_usb_load_partial_rom_patch_7668(struct LD_btmtk_usb_data *data,
+ u32 patch_len, int offset)
+{
+ u8 *pos = NULL;
+ u8 phase = 0;
+ s32 sent_len = 0;
+ u32 cur_len = 0;
+ int real_len = 0;
+ int first_block = 1;
+ int ret = 0;
+ void *buf = NULL;
+
+ buf = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!buf) {
+ return -ENOMEM;
+ }
+ pos = buf;
+
+ /* loading rom patch */
+ while (1) {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - PATCH_HEADER_SIZE_BULK_EP;
+
+ real_len = 0;
+ sent_len = (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+ if (sent_len > 0) {
+ if (first_block == 1) {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ } else if (sent_len == sent_len_max) {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ } else {
+ phase = PATCH_PHASE3;
+ }
+
+ /* prepare HCI header */
+ pos[0] = 0x6F;
+ pos[1] = 0xFC;
+ pos[2] = (sent_len + 5) & 0xFF;
+ pos[3] = ((sent_len + 5) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ pos[4] = 0x01;
+ pos[5] = 0x01;
+ pos[6] = (sent_len + 1) & 0xFF;
+ pos[7] = ((sent_len + 1) >> 8) & 0xFF;
+
+ pos[8] = phase;
+
+ os_memcpy(&pos[9], data->rom_patch + offset + cur_len, sent_len);
+ //usb_debug("sent_len = %d, cur_len = %d, phase = %d\n", sent_len, cur_len, phase);
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, buf,
+ sent_len + PATCH_HEADER_SIZE_BULK_EP, &real_len, 0);
+ if (ret) {
+ usb_debug("upload rom_patch err: %d\n", ret);
+ ret = -1;
+ goto free;
+ }
+ cur_len += sent_len;
+ MTK_MDELAY(1);
+ btmtk_usb_get_rom_patch_result(data);
+ MTK_MDELAY(1);
+
+ } else {
+ usb_debug("loading rom patch... Done\n");
+ break;
+ }
+ os_memset(buf, 0, UPLOAD_PATCH_UNIT);
+ }
+free:
+ os_kfree(buf);
+ buf = NULL;
+
+ return ret;
+}
+
+static int btmtk_usb_load_rom_patch_7668(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ int patch_status = 0;
+ int retry = 20;
+ unsigned char *tmp_str = NULL;
+ BOOL sysram3 = FALSE;
+ u32 patch_len = 0;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name,
+ NULL, data->udev, &data->rom_patch_len);
+ if (!data->rom_patch || !data->rom_patch_len) {
+ usb_debug("please assign a rom patch from (/etc/firmware/%s) or (/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name, data->rom_patch_bin_file_name);
+ return -1;
+ }
+
+ if (is_mt7668(data))
+ sysram3 = data->rom_patch_len > (PATCH_INFO_SIZE + PATCH_LEN_ILM) ? TRUE : FALSE;
+
+ do {
+ patch_status = btmtk_usb_check_need_load_patch_7668(data);
+ usb_debug("patch_status: %d, retry: %d\n", patch_status, retry);
+ if (patch_status > PATCH_NEED_DOWNLOAD || patch_status == PATCH_ERR) {
+ usb_debug("%s: patch_status error\n", __func__);
+ return -1;
+ } else if (patch_status == PATCH_READY) {
+ if (sysram3 == TRUE) {
+ usb_debug("%s: Prepare to load sysram3\n", __func__);
+ goto sysram;
+ }
+ usb_debug("%s: No need to load ROM patch\n", __func__);
+ return 0;
+ } else if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ MTK_MDELAY(100);
+ retry--;
+ } else if (patch_status == PATCH_NEED_DOWNLOAD) {
+ if (is_mt7663(data)) {
+ ret = btmtk_usb_send_wmt_cfg(data);
+ if (ret < 0) {
+ usb_debug("send wmt cmd failed(%d)\n", ret);
+ return ret;
+ }
+ }
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == PATCH_IS_DOWNLOAD_BY_OTHER) {
+ usb_debug("Hold by another fun more than 2 seconds");
+ return -1;
+ }
+
+ tmp_str = data->rom_patch;
+ SHOW_FW_DETAILS("FW Version");
+ SHOW_FW_DETAILS("build Time");
+
+ tmp_str = data->rom_patch + 16;
+ usb_debug("platform = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 20;
+ usb_debug("HW/SW version = %c%c%c%c\n", tmp_str[0], tmp_str[1], tmp_str[2], tmp_str[3]);
+
+ tmp_str = data->rom_patch + 24;
+
+ usb_debug("%s: loading rom patch of ILM\n", __func__);
+ patch_len = sysram3 ? PATCH_LEN_ILM : (data->rom_patch_len - PATCH_INFO_SIZE);
+ ret = btmtk_usb_load_partial_rom_patch_7668(data, patch_len, PATCH_INFO_SIZE);
+ if (ret < 0)
+ return ret;
+
+ /* CHIP_RESET, ROM patch would be reactivated.
+ * Currently, wmt reset is only for ILM rom patch, and there are also
+ * some preparations need to be done in FW for loading sysram3 patch...
+ */
+ MTK_MDELAY(20);
+ ret = btmtk_usb_send_wmt_reset_cmd(data);
+ if (ret < 0)
+ return ret;
+ MTK_MDELAY(20);
+
+sysram:
+ if (sysram3) {
+ usb_debug("%s: loading rom patch of sysram3\n", __func__);
+ patch_len = data->rom_patch_len - PATCH_INFO_SIZE - PATCH_LEN_ILM - PATCH_INFO_SIZE;
+ ret = btmtk_usb_load_partial_rom_patch_7668(data, patch_len,
+ PATCH_INFO_SIZE + PATCH_LEN_ILM + PATCH_INFO_SIZE);
+ }
+ return ret;
+}
+
+static void btmtk_print_bt_patch_info(struct LD_btmtk_usb_data *data)
+{
+ struct _PATCH_HEADER *patchHdr = NULL;
+ struct _Global_Descr *globalDesrc = NULL;
+
+ if (data->rom_patch == NULL) {
+ usb_debug("data->rom_patch is NULL!\n");
+ return;
+ }
+
+ patchHdr = (struct _PATCH_HEADER *)data->rom_patch;
+
+ if (is_mt7961(data))
+ globalDesrc = (struct _Global_Descr *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE);
+
+ usb_debug("[btmtk] =============== Patch Info ==============\n");
+ if (patchHdr) {
+ usb_debug("[btmtk] Built Time = %s\n", patchHdr->ucDateTime);
+ usb_debug("[btmtk] Hw Ver = 0x%04x\n", patchHdr->u2HwVer);
+ usb_debug("[btmtk] Sw Ver = 0x%04x\n", patchHdr->u2SwVer);
+ usb_debug("[btmtk] Magic Number = 0x%08x\n", patchHdr->u4MagicNum);
+
+ usb_debug("[btmtk] Platform = %c%c%c%c\n",
+ patchHdr->ucPlatform[0],
+ patchHdr->ucPlatform[1],
+ patchHdr->ucPlatform[2],
+ patchHdr->ucPlatform[3]);
+ } else
+ usb_debug("patchHdr is NULL!\n");
+
+ if (globalDesrc) {
+ usb_debug("[btmtk] Patch Ver = 0x%08x\n", globalDesrc->u4PatchVer);
+ usb_debug("[btmtk] Section num = 0x%08x\n", globalDesrc->u4SectionNum);
+ } else
+ usb_debug("globalDesrc is NULL!\n");
+ usb_debug("[btmtk] =========================================\n");
+}
+
+int btmtk_cif_send_control_out(struct LD_btmtk_usb_data *data,
+ const uint8_t *cmd, const int cmd_len, int delay, int retry)
+{
+ int ret = 0;
+
+ if (data == NULL || data->udev == NULL || data->io_buf == NULL || cmd == NULL) {
+ usb_debug("%s: incorrect cmd pointer\n", __func__);
+ ret = -1;
+ return ret;
+ }
+
+ /* send WMT command */
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_TX_EP, 0x01,
+ DEVICE_CLASS_REQUEST_OUT, 0x30, 0x00, (void *)cmd, cmd_len,
+ CONTROL_TIMEOUT_JIFFIES);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int btmtk_cif_send_bulk_out(struct LD_btmtk_usb_data *data,
+ const uint8_t *cmd, const int cmd_len)
+{
+ int ret = 0;
+ int real_len = 0;
+
+ if (data == NULL || data->udev == NULL || cmd == NULL) {
+ usb_debug("%s: incorrect cmd pointer\n", __func__);
+ ret = -1;
+ return ret;
+ }
+
+ ret = data->hcif->usb_bulk_msg(data->udev, MTKBT_BULK_TX_EP, cmd, cmd_len, &real_len, 0);
+ if (ret < 0) {
+ usb_debug("command send failed(%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+int btmtk_cif_send_cmd(struct LD_btmtk_usb_data *data, const uint8_t *cmd,
+ const int cmd_len, int delay, int retry, int endpoint)
+{
+ int ret = -1;
+
+ if (endpoint == BTMTK_EP_TYPE_OUT_CMD) {
+ /* handle wmt cmd from driver */
+ ret = btmtk_cif_send_control_out(data, cmd, cmd_len,
+ delay, retry);
+ } else if (endpoint == BTMTK_EP_TPYE_OUT_ACL) {
+ /* bulk out for load rom patch*/
+ ret = btmtk_cif_send_bulk_out(data, cmd, cmd_len);
+ }
+
+ return ret;
+}
+
+int btmtk_cif_recv_evt(struct LD_btmtk_usb_data *data, int delay, int retry)
+{
+ int ret = -1; /* if successful, 0 */
+
+ if (!data) {
+ usb_debug("%s: data == NULL!\n", __func__);
+ return ret;
+ }
+
+ if (!data->udev) {
+ usb_debug("%s: invalid parameters!\n", __func__);
+ return ret;
+ }
+
+get_response_again:
+ /* ms delay */
+ MTK_MDELAY(delay);
+
+ /* check WMT event */
+ memset(data->io_buf, 0, LD_BT_MAX_EVENT_SIZE);
+ ret = data->hcif->usb_control_msg(data->udev, MTKBT_CTRL_RX_EP,
+ 0x01, DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, CONTROL_TIMEOUT_JIFFIES);
+
+ if (ret < 0) {
+ usb_debug("%s: event get failed(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ if (ret > 0) {
+ //usb_debug_raw(data->io_buf, ret, "%s OK: EVT:", __func__);
+ return ret; /* return read length */
+ } else if (retry > 0) {
+ usb_debug("%s: Trying to get response... (%d)\n", __func__, ret);
+ retry--;
+ goto get_response_again;
+ } else
+ usb_debug("%s NG: do not got response:(%d)\n", __func__, ret);
+
+ return -1;
+}
+
+int btmtk_compare_evt(struct LD_btmtk_usb_data *data, const uint8_t *event,
+ int event_len, int recv_evt_len)
+{
+ int ret = -1;
+
+ if (data && data->io_buf && event && recv_evt_len >= event_len) {
+ if (memcmp(data->io_buf, event, event_len) == 0) {
+ ret = 0;
+ goto exit;
+ } else {
+ usb_debug("%s compare fail\n", __func__);
+ usb_debug_raw(event, event_len, "%s: event_need_compare:", __func__);
+ usb_debug_raw(data->io_buf, recv_evt_len, "%s: RCV:", __func__);
+ goto exit;
+ }
+ } else
+ usb_debug("%s invalid parameter!\n", __func__);
+
+exit:
+ return ret;
+}
+
+int btmtk_main_send_cmd(struct LD_btmtk_usb_data *data, const uint8_t *cmd,
+ const int cmd_len, const uint8_t *event, const int event_len, int delay,
+ int retry, int endpoint)
+{
+ int ret = 0;
+
+ if (data == NULL || cmd == NULL) {
+ usb_debug("%s, invalid parameters!\n", __func__);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = btmtk_cif_send_cmd(data, cmd, cmd_len, delay, retry, endpoint);
+ if (ret < 0) {
+ usb_debug("%s btmtk_cif_send_cmd failed!!\n", __func__);
+ return ret;
+ }
+
+ /* wmt cmd and download fw patch using wmt cmd with USB interface, need use
+ * usb_control_msg to recv wmt event;
+ */
+ if (event && (endpoint == BTMTK_EP_TYPE_OUT_CMD || endpoint == BTMTK_EP_TPYE_OUT_ACL)) {
+ data->recv_evt_len = btmtk_cif_recv_evt(data, delay, retry);
+ if (data->recv_evt_len < 0) {
+ usb_debug("%s btmtk_cif_recv_evt failed!!\n", __func__);
+ ret = -1;
+ return ret;
+ }
+ ret = btmtk_compare_evt(data, event, event_len, data->recv_evt_len);
+ }
+
+ return ret;
+}
+
+static int btmtk_send_wmt_download_cmd(struct LD_btmtk_usb_data *data,
+ u8 *cmd, int cmd_len, u8 *event, int event_len, struct _Section_Map *sectionMap,
+ u8 fw_state, u8 dma_flag)
+{
+ int payload_len = 0;
+ int ret = -1;
+
+ if (data == NULL || cmd == NULL || event == NULL || sectionMap == NULL) {
+ usb_debug("%s: invalid parameter!", __func__);
+ return ret;
+ }
+
+ /* need refine this cmd to mtk_wmt_hdr struct*/
+ /* prepare HCI header */
+ cmd[0] = 0x6F;
+ cmd[1] = 0xFC;
+
+ /* prepare WMT header */
+ cmd[3] = 0x01;
+ cmd[4] = 0x01; /* opcode */
+
+ if (fw_state == 0) {
+ /* prepare WMT DL cmd */
+ payload_len = SEC_MAP_NEED_SEND_SIZE + 2;
+
+ cmd[2] = (payload_len + WMT_HEADER_LEN) & 0xFF; /* length*/
+ cmd[5] = payload_len & 0xFF;
+ cmd[6] = (payload_len >> 8) & 0xFF;
+ cmd[7] = 0x00; /* which is the FW download state 0 */
+ cmd[8] = dma_flag; /* 1:using DMA to download, 0:using legacy wmt cmd*/
+ cmd_len = SEC_MAP_NEED_SEND_SIZE + PATCH_HEADER_SIZE_BULK_EP;
+
+ memcpy(&cmd[PATCH_HEADER_SIZE_BULK_EP], (u8 *)(sectionMap->u4SecSpec), SEC_MAP_NEED_SEND_SIZE);
+
+ ret = btmtk_main_send_cmd(data, cmd, cmd_len,
+ event, event_len, PATCH_DOWNLOAD_CMD_DELAY_TIME, PATCH_DOWNLOAD_CMD_RETRY, BTMTK_EP_TYPE_OUT_CMD);
+ if (ret < 0) {
+ usb_debug("%s: send wmd dl cmd failed, terminate!\n", __func__);
+ return PATCH_ERR;
+ }
+
+ if (data->recv_evt_len >= event_len)
+ return data->io_buf[PATCH_STATUS];
+
+ return PATCH_ERR;
+ } else
+ usb_debug("%s: fw state is error!\n", __func__);
+
+ return ret;
+}
+
+static int btmtk_load_fw_patch_using_wmt_cmd(struct LD_btmtk_usb_data *data,
+ u8 *image, u8 *event, int event_len, u32 patch_len, int offset)
+{
+/* Using Bulk EP will cause Buzzard no response
+ * Need check later
+ */
+#define DL_PATCH_BY_BULK_EP 0
+ int ret = 0;
+ u32 cur_len = 0;
+ s32 sent_len;
+ int first_block = 1;
+ u8 phase;
+ int delay = PATCH_DOWNLOAD_PHASE1_2_DELAY_TIME;
+ int retry = PATCH_DOWNLOAD_PHASE1_2_RETRY;
+#if DL_PATCH_BY_BULK_EP
+ s32 sent_hdr_len = PATCH_HEADER_SIZE_BULK_EP;
+ int ep = BTMTK_EP_TPYE_OUT_ACL;
+#else
+ s32 sent_hdr_len = PATCH_HEADER_SIZE_CTRL_EP;
+ int ep = BTMTK_EP_TYPE_OUT_CMD;
+#endif
+
+ if (data == NULL || image == NULL || data->rom_patch == NULL) {
+ usb_debug("%s, invalid parameters!\n", __func__);
+ ret = -1;
+ goto exit;
+ }
+ /* loading rom patch */
+ while (1) {
+ s32 sent_len_max = UPLOAD_PATCH_UNIT - sent_hdr_len;
+
+ sent_len = (patch_len - cur_len) >= sent_len_max ? sent_len_max : (patch_len - cur_len);
+
+ if (sent_len > 0) {
+ if (first_block == 1) {
+ if (sent_len < sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE1;
+ first_block = 0;
+ } else if (sent_len == sent_len_max) {
+ if (patch_len - cur_len == sent_len_max)
+ phase = PATCH_PHASE3;
+ else
+ phase = PATCH_PHASE2;
+ } else {
+ phase = PATCH_PHASE3;
+ }
+
+#if DL_PATCH_BY_BULK_EP
+ /* prepare HCI header */
+ image[0] = 0x6F;
+ image[1] = 0xFC;
+ image[2] = (sent_len + LOAD_PATCH_PHASE_LEN + WMT_HEADER_LEN) & 0xFF;
+ image[3] = ((sent_len + LOAD_PATCH_PHASE_LEN + WMT_HEADER_LEN) >> 8) & 0xFF;
+
+ /* prepare WMT header */
+ image[4] = 0x01;
+ image[5] = 0x01;
+ image[6] = (sent_len + LOAD_PATCH_PHASE_LEN) & 0xFF;
+ image[7] = ((sent_len + LOAD_PATCH_PHASE_LEN) >> 8) & 0xFF;
+
+ image[8] = phase;
+#else
+ /* prepare HCI header */
+ image[0] = 0x6F;
+ image[1] = 0xFC;
+ image[2] = sent_len + LOAD_PATCH_PHASE_LEN + WMT_HEADER_LEN;
+
+ /* prepare WMT header */
+ image[3] = 0x01;
+ image[4] = 0x01;
+ image[5] = (sent_len + LOAD_PATCH_PHASE_LEN) & 0xFF;
+ image[6] = ((sent_len + LOAD_PATCH_PHASE_LEN) >> 8) & 0xFF;
+
+ image[7] = phase;
+#endif
+ memcpy(&image[sent_hdr_len], data->rom_patch + offset + cur_len, sent_len);
+ if (phase == PATCH_PHASE3) {
+ delay = PATCH_DOWNLOAD_PHASE3_DELAY_TIME;
+ retry = PATCH_DOWNLOAD_PHASE3_RETRY;
+ }
+
+ cur_len += sent_len;
+ ret = btmtk_main_send_cmd(data, image, sent_len + sent_hdr_len,
+ event, event_len, delay, retry, ep);
+ if (ret < 0) {
+ usb_debug("%s: send patch failed, terminate\n", __func__);
+ goto exit;
+ }
+ } else
+ break;
+ }
+
+exit:
+ return ret;
+}
+
+static int btmtk_send_fw_rom_patch_79xx(struct LD_btmtk_usb_data *data)
+{
+ u8 *pos = NULL;
+ int loop_count = 0;
+ int ret = 0;
+ u32 section_num = 0;
+ u32 section_offset = 0;
+ u32 dl_size = 0;
+ int patch_status = 0;
+ int retry = 20;
+ u8 dma_flag = PATCH_DOWNLOAD_USING_WMT;
+ struct _Section_Map *sectionMap;
+ struct _Global_Descr *globalDescr;
+ u8 event[] = {0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[6] is status*/
+
+ if (data->rom_patch == NULL) {
+ usb_debug("data->rom_patch is NULL!");
+ ret = -1;
+ goto exit;
+ }
+
+ globalDescr = (struct _Global_Descr *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE);
+
+ usb_debug("%s: loading rom patch...\n", __func__);
+
+ section_num = globalDescr->u4SectionNum;
+ usb_debug("%s: section_num = 0x%08x\n", __func__, section_num);
+
+ pos = os_kzalloc(UPLOAD_PATCH_UNIT, MTK_GFP_ATOMIC);
+ if (!pos) {
+ usb_debug("%s: alloc memory failed\n", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ do {
+ sectionMap = (struct _Section_Map *)(data->rom_patch + FW_ROM_PATCH_HEADER_SIZE +
+ FW_ROM_PATCH_GD_SIZE + FW_ROM_PATCH_SEC_MAP_SIZE * loop_count);
+
+ section_offset = sectionMap->u4SecOffset;
+ dl_size = sectionMap->bin_info_spec.u4DLSize;
+
+ usb_debug("%s: loop_count = %d, section_offset = 0x%08x, download patch_len = 0x%08x\n",
+ __func__, loop_count, section_offset, dl_size);
+
+ if (dl_size > 0) {
+ retry = 20;
+ do {
+ patch_status = btmtk_send_wmt_download_cmd(data, pos, 0,
+ event, sizeof(event) - 1, sectionMap, 0, dma_flag);
+ usb_debug("%s: patch_status %d\n", __func__, patch_status);
+
+ if (patch_status > BUZZARD_PATCH_READY || patch_status == PATCH_ERR) {
+ usb_debug("%s: patch_status error\n", __func__);
+ ret = -1;
+ goto err;
+ } else if (patch_status == BUZZARD_PATCH_READY) {
+ usb_debug("%s: no need to load rom patch section%d\n", __func__, loop_count);
+ goto next_section;
+ } else if (patch_status == BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER) {
+ MTK_MDELAY(100);
+ retry--;
+ } else if (patch_status == BUZZARD_PATCH_NEED_DOWNLOAD) {
+ break; /* Download ROM patch directly */
+ }
+ } while (retry > 0);
+
+ if (patch_status == BUZZARD_PATCH_IS_DOWNLOAD_BY_OTHER) {
+ usb_debug("%s: Hold by another fun more than 2 seconds\n", __func__);
+ ret = -1;
+ goto err;
+ }
+
+ if (dma_flag == PATCH_DOWNLOAD_USING_DMA) {
+ /* using DMA to download fw patch*/
+ } else {
+ /* using legacy wmt cmd to download fw patch */
+ ret = btmtk_load_fw_patch_using_wmt_cmd(data, pos, event,
+ sizeof(event) - 1, dl_size, section_offset);
+ if (ret < 0) {
+ usb_debug("%s: btmtk_load_fw_patch_using_wmt_cmd failed!\n", __func__);
+ goto err;
+ }
+ }
+ }
+
+next_section:
+ continue;
+ } while (++loop_count < section_num);
+
+err:
+ os_kfree(pos);
+ pos = NULL;
+
+exit:
+ usb_debug("%s: loading rom patch... Done\n", __func__);
+ return ret;
+}
+
+int btmtk_load_rom_patch_79xx(struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+
+ LD_load_code_from_bin(&data->rom_patch, (char *)data->rom_patch_bin_file_name,
+ NULL, data->udev, &data->rom_patch_len);
+ if (!data->rom_patch || !data->rom_patch_len) {
+ usb_debug("please assign a rom patch from (/etc/firmware/%s) or (/lib/firmware/%s)\n",
+ data->rom_patch_bin_file_name, data->rom_patch_bin_file_name);
+ return -1;
+ }
+
+ btmtk_print_bt_patch_info(data);
+
+ ret = btmtk_send_fw_rom_patch_79xx(data);
+ if (ret < 0) {
+ usb_debug("%s, btmtk_send_fw_rom_patch_79xx failed!\n", __func__);
+ return ret;
+ }
+
+ usb_debug("btmtk_load_rom_patch_79xx end\n");
+
+ return ret;
+}
+
+static int btmtk_usb_load_fw_cfg_setting(char *block_name, struct fw_cfg_struct *save_content,
+ int counter, u8 *searchcontent, enum fw_cfg_index_len index_length)
+{
+ int ret = 0, i = 0;
+ unsigned int temp_len = 0;
+ u8 temp[260]; /* save for total hex number */
+ char *search_result = NULL;
+ char *search_end = NULL;
+ char search[32];
+ char *next_block = NULL;
+#define CHAR2HEX_SIZE 4
+ char number[CHAR2HEX_SIZE + 1]; /* 1 is for '\0' */
+
+ memset(search, 0, sizeof(search));
+ memset(temp, 0, sizeof(temp));
+ memset(number, 0, sizeof(number));
+
+ /* search block name */
+ for (i = 0; i < counter; i++) {
+ temp_len = 0;
+ if (index_length == FW_CFG_INX_LEN_2) /* EX: APCF01 */
+ (void)snprintf(search, sizeof(search), "%s%02d:", block_name, i);
+ else if (index_length == FW_CFG_INX_LEN_3) /* EX: APCF001 */
+ (void)snprintf(search, sizeof(search), "%s%03d:", block_name, i);
+ else
+ (void)snprintf(search, sizeof(search), "%s:", block_name);
+ search_result = strstr((char *)searchcontent, search);
+
+ if (search_result) {
+ memset(temp, 0, sizeof(temp));
+ search_result = strstr(search_result, "0x");
+ if (search_result == NULL) {
+ usb_debug("%s: search_result is NULL", __func__);
+ return -1;
+ }
+
+ /* find next line as end of this command line, if NULL means last line */
+ next_block = strstr(search_result, ":");
+
+ do {
+ search_end = strstr(search_result, ",");
+ if (search_end == NULL) {
+ usb_debug("%s: search_end is NULL", __func__);
+ break;
+ }
+
+ if (search_end - search_result != CHAR2HEX_SIZE) {
+ usb_debug("Incorrect Format in %s\n", search);
+ break;
+ }
+
+ memset(number, 0, sizeof(number));
+ memcpy(number, search_result, CHAR2HEX_SIZE);
+
+ temp[temp_len++] = strtol(number, &number, 0);
+ search_result = strstr(search_end, "0x");
+ if (search_result == NULL) {
+ usb_debug("%s: search_result is NULL", __func__);
+ break;
+ }
+ } while (search_result < next_block || (search_result && next_block == NULL));
+ }
+
+ if (temp_len && temp_len < sizeof(temp)) {
+ usb_debug("%s found & stored in %d\n", search, i);
+ save_content[i].content = os_kzalloc(temp_len, MTK_GFP_ATOMIC);
+ if (save_content[i].content == NULL) {
+ usb_debug("Allocate memory fail(%d)\n", i);
+ return -ENOMEM;
+ }
+ memcpy(save_content[i].content, temp, temp_len);
+ save_content[i].length = temp_len;
+ }
+ }
+
+ return ret;
+}
+
+int btmtk_usb_load_woble_setting(struct LD_btmtk_usb_data *data)
+{
+ int err = 0;
+ BOOL woble_setting_bin = FALSE;
+ BOOL wake_on_ble_conf = FALSE;
+
+ if (!data)
+ return -EINVAL;
+
+ /* For woble_setting.bin */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ }
+ data->setting_file_len = 0;
+
+ LD_load_code_from_bin(&data->setting_file, data->woble_setting_file_name,
+ NULL, data->udev, &data->setting_file_len);
+
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("Please make sure %s in the /vendor/firmware\n",
+ data->woble_setting_file_name);
+ woble_setting_bin = FALSE;
+ } else {
+ err = btmtk_usb_load_fw_cfg_setting("APCF", data->woble_setting_apcf,
+ WOBLE_SETTING_COUNT, data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("APCF_ADD_MAC",
+ data->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT,
+ data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("APCF_ADD_MAC_LOCATION",
+ data->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT,
+ data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ err = btmtk_usb_load_fw_cfg_setting("RADIOOFF", &data->woble_setting_radio_off,
+ 1, data->setting_file, FW_CFG_INX_LEN_2);
+ if (err)
+ goto LOAD_ERR;
+
+ switch (data->bt_cfg.unify_woble_type) {
+ case 0:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_LEGACY",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 1:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_WAVEFORM",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ case 2:
+ err = btmtk_usb_load_fw_cfg_setting("WAKEUP_TYPE_IR",
+ &data->woble_setting_wakeup_type, 1, data->setting_file, FW_CFG_INX_LEN_2);
+ break;
+ default:
+ usb_debug("unify_woble_type unknown(%d)\n", data->bt_cfg.unify_woble_type);
+ }
+ if (err) {
+ usb_debug("Parse unify_woble_type(%d) failed\n", data->bt_cfg.unify_woble_type);
+ goto LOAD_ERR;
+ }
+
+ err = btmtk_usb_load_fw_cfg_setting("RADIOOFF_COMPLETE_EVENT",
+ &data->woble_setting_radio_off_comp_event, 1, data->setting_file,
+ FW_CFG_INX_LEN_2);
+
+LOAD_ERR:
+ if (data->setting_file) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ data->setting_file_len = 0;
+ }
+
+ if (err) {
+ woble_setting_bin = FALSE;
+ usb_debug("err(%d), woble_setting_bin(%d)\n", err, woble_setting_bin);
+ } else
+ woble_setting_bin = TRUE;
+ }
+
+ /* For wake_on_ble.conf */
+ data->wake_dev = NULL;
+ data->wake_dev_len = 0;
+
+ LD_load_code_from_bin(&data->wake_dev, WAKE_DEV_RECORD, WAKE_DEV_RECORD_PATH,
+ data->udev, &data->wake_dev_len);
+ if (data->wake_dev == NULL || data->wake_dev_len == 0) {
+ usb_debug("There is no DEVICE RECORD for wake-up\n");
+ wake_on_ble_conf = FALSE;
+ } else {
+ // content check
+ if (data->wake_dev[0] != data->wake_dev_len || data->wake_dev_len < 3) {
+ usb_debug("Incorrect total length on %s\n", WAKE_DEV_RECORD);
+ data->wake_dev_len = 0;
+ os_kfree(data->wake_dev);
+ data->wake_dev = NULL;
+ wake_on_ble_conf = FALSE;
+ } else {
+ wake_on_ble_conf = TRUE;
+ }
+ }
+
+ if (woble_setting_bin == FALSE && wake_on_ble_conf == FALSE)
+ return -ENOENT;
+ return 0;
+}
+
+static int btmtk_usb_set_unify_woble(struct LD_btmtk_usb_data *data)
+{
+ int ret = -1;
+ u8 *radio_off = NULL;
+ int length = 0;
+ // Filter Index: 0x5A
+ u8 cmd[] = { 0xC9, 0xFC, 0x14, 0x01, 0x20, 0x02, 0x00, 0x01, 0x02, 0x01,
+ 0x00, 0x05, 0x10, 0x01, 0x00, 0x40, 0x06, 0x02, 0x40, FIDX, 0x02,
+ 0x41, 0x0F};
+ u8 event[] = { 0xE6, 0x02, 0x08, 0x00 };
+
+ if (is_mt7961(data)) {
+ cmd[FIDX_OFFSET_RADIO_OFF] = BUZZARD_FIDX;
+ }
+
+ if (data->woble_setting_radio_off.length && is_support_unify_woble(data)) {
+ /* start to send radio off cmd from woble setting file */
+ length = data->woble_setting_radio_off.length +
+ data->woble_setting_wakeup_type.length;
+ radio_off = os_kzalloc(length, MTK_GFP_ATOMIC);
+ if (!radio_off) {
+ usb_debug("alloc memory fail (radio_off)\n");
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ memcpy(radio_off, data->woble_setting_radio_off.content,
+ data->woble_setting_radio_off.length);
+ if (data->woble_setting_wakeup_type.length) {
+ memcpy(radio_off + data->woble_setting_radio_off.length,
+ data->woble_setting_wakeup_type.content,
+ data->woble_setting_wakeup_type.length);
+ radio_off[2] += data->woble_setting_wakeup_type.length;
+ }
+
+ usb_debug_raw(radio_off, length, "Send radio off");
+ ret = btmtk_usb_send_hci_cmd(data, radio_off,
+ length,
+ data->woble_setting_radio_off_comp_event.content,
+ data->woble_setting_radio_off_comp_event.length);
+ if (ret < 0) {
+ usb_debug("btmtk_usb_send_hci_cmd return fail %d\n", ret);
+ return ret;
+ }
+ }else { /* use default */
+ usb_debug("use default radio off cmd\n");
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event, sizeof(event));
+ if (ret < 0)
+ usb_debug("Failed(%d)\n", ret);
+ }
+ return ret;
+}
+
+static void btmtk_usb_free_fw_cfg_struct(struct fw_cfg_struct *fw_cfg, int count)
+{
+ int i = 0;
+
+ for (i = 0; i < count; i++) {
+ if (fw_cfg[i].content) {
+ os_kfree(fw_cfg[i].content);
+ fw_cfg[i].content = NULL;
+ fw_cfg[i].length = 0;
+ } else
+ fw_cfg[i].length = 0;
+ }
+ return;
+}
+
+static void btmtk_usb_free_setting_file(struct LD_btmtk_usb_data *data)
+{
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf_fill_mac, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(data->woble_setting_apcf_fill_mac_location, WOBLE_SETTING_COUNT);
+ btmtk_usb_free_fw_cfg_struct(&data->woble_setting_radio_off, 1);
+ btmtk_usb_free_fw_cfg_struct(&data->woble_setting_radio_off_comp_event, 1);
+ btmtk_usb_free_fw_cfg_struct(data->bt_cfg.wmt_cmd, WMT_CMD_COUNT);
+
+ memset(&data->bt_cfg, 0, sizeof(data->bt_cfg));
+
+ if (data->woble_setting_file_name) {
+ os_kfree(data->woble_setting_file_name);
+ data->woble_setting_file_name = NULL;
+ }
+ return;
+}
+
+static int btmtk_usb_parse_bt_cfg_file(char *item_name, char *text, u8 *searchcontent)
+{
+ int ret = 0;
+ int temp_len = 0;
+ char search[32];
+ char *ptr = NULL, *p = NULL;
+ char *temp = text;
+
+ if (text == NULL) {
+ usb_debug("text param is invalid!\n");
+ ret = false;
+ goto out;
+ }
+
+ memset(search, 0, sizeof(search));
+ (void)snprintf(search, sizeof(search), "%s", item_name); /* EX: SUPPORT_UNIFY_WOBLE */
+ p = ptr = strstr((char *)searchcontent, search);
+
+ if (!ptr) {
+ usb_debug("Can't find %s\n", item_name);
+ ret = -1;
+ goto out;
+ }
+
+ if (p > (char *)searchcontent) {
+ p--;
+ while ((*p == ' ') && (p != (char *)searchcontent))
+ p--;
+ if (*p == '#') {
+ usb_debug("It's invalid bt cfg item\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ p = ptr + strlen(item_name) + 1;
+ ptr = p;
+
+ for (;;) {
+ switch (*p) {
+ case '\n':
+ goto textdone;
+ default:
+ *temp++ = *p++;
+ break;
+ }
+ }
+
+textdone:
+ temp_len = p - ptr;
+ *temp = '\0';
+
+out:
+ return ret;
+}
+
+static int btmtk_usb_load_bt_cfg_item(struct bt_cfg_struct *bt_cfg_content, u8 *searchcontent)
+{
+ int ret = 0;
+ char text[128]; /* save for search text */
+
+ memset(text, 0, sizeof(text));
+ ret = btmtk_usb_parse_bt_cfg_file(BT_UNIFY_WOBLE, text, searchcontent);
+ if (!ret) {
+ bt_cfg_content->support_unify_woble = strtol(text, &text, 10);
+ usb_debug("bt_cfg_content->support_unify_woble = %d\n", bt_cfg_content->support_unify_woble);
+ } else {
+ usb_debug("search item %s is invalid!\n", BT_UNIFY_WOBLE);
+ }
+
+ ret = btmtk_usb_parse_bt_cfg_file(BT_UNIFY_WOBLE_TYPE, text, searchcontent);
+ if (!ret) {
+ bt_cfg_content->unify_woble_type = strtol(text, &text, 10);
+ usb_debug("bt_cfg_content->unify_woble_type = %d\n", bt_cfg_content->unify_woble_type);
+ } else {
+ usb_debug("search item %s is invalid!\n", BT_UNIFY_WOBLE_TYPE);
+ }
+
+ ret = btmtk_usb_load_fw_cfg_setting(BT_WMT_CMD, bt_cfg_content->wmt_cmd,
+ WMT_CMD_COUNT, searchcontent, FW_CFG_INX_LEN_3);
+ if (ret)
+ usb_debug("%s: search item %s is invalid!", __func__, BT_WMT_CMD);
+ return ret;
+}
+
+static int btmtk_usb_load_bt_cfg(char *cfg_name, struct LD_btmtk_usb_data *data)
+{
+ int ret = 0;
+ char bt_cfg_name[MAX_BIN_FILE_NAME_LEN] = {'\0'};
+
+ if (!data)
+ return -EINVAL;
+
+ /* For woble_setting.bin */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ }
+ data->setting_file_len = 0;
+
+ if (data->flavor)
+ (void)snprintf(bt_cfg_name, MAX_BIN_FILE_NAME_LEN, "%s%x_1a_%x.%s", BT_CFG_NAME_PREFIX,
+ data->chip_id & 0xffff, (data->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ else
+ (void)snprintf(bt_cfg_name, MAX_BIN_FILE_NAME_LEN, "%s%x_1_%x.%s", BT_CFG_NAME_PREFIX,
+ data->chip_id & 0xffff, (data->fw_version & 0xff) + 1, BT_CFG_NAME_SUFFIX);
+ LD_load_code_from_bin(&data->setting_file, bt_cfg_name, NULL, data->udev, &data->setting_file_len);
+
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("%s not exist in the /vendor/firmware\n", bt_cfg_name);
+
+ LD_load_code_from_bin(&data->setting_file, BT_CFG_NAME, NULL, data->udev, &data->setting_file_len);
+ if (data->setting_file == NULL || data->setting_file_len == 0) {
+ usb_debug("%s not exist in the /vendor/firmware\n", BT_CFG_NAME);
+ usb_debug("Please make sure %s or %s in the /vendor/firmware\n", bt_cfg_name, BT_CFG_NAME);
+ return -ENOMEM;
+ }
+ }
+
+ ret = btmtk_usb_load_bt_cfg_item(&data->bt_cfg, data->setting_file);
+ if (ret) {
+ usb_debug("btmtk_usb_load_bt_cfg_item error return %d\n", ret);
+ return ret;
+ }
+
+ /* release setting file memory */
+ if (data->setting_file != NULL) {
+ os_kfree(data->setting_file);
+ data->setting_file = NULL;
+ data->setting_file_len = 0;
+ }
+ return ret;
+}
+
+static void btmtk_usb_initialize_cfg_items(struct LD_btmtk_usb_data *data)
+{
+ if (data == NULL) {
+ usb_debug("btmtk data NULL!\n");
+ return;
+ }
+
+ data->bt_cfg.support_unify_woble = 1;
+ data->bt_cfg.unify_woble_type = 0;
+ btmtk_usb_free_fw_cfg_struct(data->bt_cfg.wmt_cmd, WMT_CMD_COUNT);
+}
+
+/*============================================================================*/
+/* Interface Functions */
+/*============================================================================*/
+int Ldbtusb_getBtWakeT(struct LD_btmtk_usb_data *data){
+ //struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+ int ret = -1;
+ if (!data) {
+ usb_debug("btmtk data NULL!\n");
+ return ret;
+ }
+ if (is_mt7668(data)) {
+ usb_debug("is_mt7668(data \n");
+ //resume
+ u8 buf[] = {0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00};
+ u8 event[] = { 0xe6, 0x02, 0x08, 0x01 };
+ ret = btmtk_usb_send_hci_cmd(data, buf, sizeof(buf), event, sizeof(event));
+
+ if (ret < 0)
+ {
+ usb_debug("%s error1(%d)\n", __func__, ret);
+ return ret;
+ }
+ u8 cmd[] = { 0xCE, 0xFC, 0x04, 0x03, 0x00, 0x00, 0x00};
+ u8 event1[] = { 0xe8 };
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event1, sizeof(event1));
+ int len = 0;
+ u8 retry = 0;
+ u8 returnEvent = 0xe8;
+ usb_debug("\n");
+ do {
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, USB_INTR_MSG_TIMO);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ }
+ if (returnEvent == data->io_buf[0]) {//get response from fw
+ usb_debug("mode %d\n",data->io_buf[WOBLE_LOG_OFFSET_76XX]);
+ if(data->io_buf[WOBLE_LOG_OFFSET_76XX] == WOBLE_LOG_VALUE_76XX) {//get soundmode bt wake type
+ //Xgimi_Set_MusicMode(TRUE);
+ }
+ break;
+ } else {
+ usb_debug("\n");
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ usb_debug("try get event again\n");
+ } while (retry < 5);
+ } else {
+ usb_debug("is_mt7662 data \n");
+ btmtk_usb_send_hci_reset_cmd(data);
+ u8 buf[] = {0xC9, 0xFC, 0x05, 0x01, 0x21, 0x02, 0x00, 0x00};
+ u8 event[] = { 0xe6, 0x02, 0x08, 0x01 };
+ ret = btmtk_usb_send_hci_cmd(data, buf, sizeof(buf), event, sizeof(event));
+
+ if (ret < 0) {
+ usb_debug("%s error1(%d)\n", __func__, ret);
+ return ret;
+ }
+ u8 cmd[] = { 0x4B, 0xFC, 0x04, 0x03, 0x00, 0x00, 0x00};
+ u8 event1[] = { 0x0f };
+ ret = btmtk_usb_send_hci_cmd(data, cmd, sizeof(cmd), event1, sizeof(event1));
+ int len = 0;
+ u8 retry = 0;
+ u8 returnEvent = 0xe8;
+
+ usb_debug("after send\n");
+ do {
+ ret = data->hcif->usb_interrupt_msg(data->udev, MTKBT_INTR_EP, data->io_buf,
+ LD_BT_MAX_EVENT_SIZE, &len, USB_INTR_MSG_TIMO);
+ if (ret < 0) {
+ usb_debug("event get failed: %d\n", ret);
+ }
+ if (returnEvent == data->io_buf[0]) {//get response from fw
+ usb_debug("mode %d\n",data->io_buf[WOBLE_LOG_OFFSET_76XX]);
+ if(data->io_buf[WOBLE_LOG_OFFSET_76XX] == WOBLE_LOG_VALUE_76XX){//get soundmode bt wake type
+ //Xgimi_Set_MusicMode(TRUE);
+ }
+ break;
+ } else {
+ usb_debug("\n");
+ }
+ MTK_MDELAY(10);
+ ++retry;
+ usb_debug("try get event again\n");
+ } while (retry < 5);
+ }
+ return 0;
+}
+
+u8 LD_btmtk_usb_getWoBTW(void)
+{
+ usb_debug("LD_btmtk_usb_getWoBTW, u8WoBTW = %d\n", u8WoBTW);
+ return u8WoBTW;
+}
+
+void LD_btmtk_usb_SetWoble(mtkbt_dev_t *dev)
+{
+ struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+ int ret = -1;
+ u8WoBTW = PM_SOURCE_DISABLE;
+
+ usb_debug("\n");
+ if (!data) {
+ usb_debug("btmtk data NULL!\n");
+ return;
+ }
+
+ if (is_mt7668(data) || is_mt7663(data) || is_mt7961(data)) {
+ if (is_support_unify_woble(data)) {
+ if (data->bt_cfg.unify_woble_type == UNIFY_WOBLE_WAVEFORM) {
+ /* BTW */
+ u8WoBTW = 1;
+ }
+
+ /* Power on sequence */
+ btmtk_usb_send_wmt_power_on_cmd_7668(data);
+ if (!is_mt7961(data)) {
+ btmtk_usb_send_hci_tci_set_sleep_cmd_7668(data);
+ btmtk_usb_send_hci_reset_cmd(data);
+ btmtk_usb_get_vendor_cap(data);
+ } else {
+ btmtk_usb_send_apcf_reserved_79xx(data);
+ }
+
+ btmtk_usb_send_read_bdaddr(data);
+ ret = btmtk_usb_load_woble_setting(data);
+ if (ret) {
+ usb_debug("Using lagecy WoBLE setting(%d)!!!\n", ret);
+ btmtk_usb_set_apcf(data, FALSE);
+ } else
+ btmtk_usb_set_apcf(data, TRUE);
+ btmtk_usb_set_unify_woble(data);
+ }
+ } else {
+ btmtk_usb_send_hci_suspend_cmd(data);
+ }
+
+ // Clean & free buffer
+ btmtk_usb_free_setting_file(data);
+
+ if (data->wake_dev) {
+ os_kfree(data->wake_dev);
+ data->wake_dev = NULL;
+ data->wake_dev_len = 0;
+ }
+
+ return;
+}
+
+int LD_btmtk_usb_probe(mtkbt_dev_t *dev,int flag)
+{
+ struct LD_btmtk_usb_data *data;
+ int ret=0, err = 0;
+
+ usb_debug("=========================================\n");
+ usb_debug("Mediatek Bluetooth USB driver ver %s\n", LD_VERSION);
+ usb_debug("=========================================\n");
+ os_memcpy(driver_version, LD_VERSION, sizeof(LD_VERSION));
+ probe_counter++;
+ isbtready = 0;
+ is_assert = 0;
+ //usb_debug("LD_btmtk_usb_probe begin\n");
+ usb_debug("probe_counter = %d\n", probe_counter);
+
+ data = os_kzalloc(sizeof(*data), MTK_GFP_ATOMIC);
+ if (!data) {
+ usb_debug("[ERR] end Error 1\n");
+ return -ENOMEM;
+ }
+
+ data->hcif = BT_INST(dev)->hci_if;
+
+ data->cmdreq_type = USB_TYPE_CLASS;
+
+ data->udev = dev;
+
+ data->meta_tx = 0;
+
+ data->chip_id = dev->chipid;
+
+ data->io_buf = os_kmalloc(LD_BT_MAX_EVENT_SIZE, MTK_GFP_ATOMIC);
+ if (!data->io_buf) {
+ usb_debug("Can't allocate memory io_buf\n");
+ os_kfree(data);
+ return -ENOMEM;
+ }
+
+ data->rom_patch_bin_file_name = os_kzalloc(MAX_BIN_FILE_NAME_LEN, MTK_GFP_ATOMIC);
+ if (!data->rom_patch_bin_file_name) {
+ usb_debug("Can't allocate memory rom_patch_bin_file_name\n");
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return -ENOMEM;
+ }
+
+ if (data->woble_setting_file_name == NULL) {
+ data->woble_setting_file_name = os_kzalloc(MAX_BIN_FILE_NAME_LEN, MTK_GFP_ATOMIC);
+ if (!data->woble_setting_file_name) {
+ usb_debug("Can't allocate memory woble_setting_file_name\n");
+ os_kfree(data->rom_patch_bin_file_name);
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return -ENOMEM;
+ }
+ }
+
+ btmtk_usb_switch_iobase(data, WLAN);
+ btmtk_usb_initialize_cfg_items(data);
+
+ /* clayton: according to the chip id, load f/w or rom patch */
+ usb_debug("data address = %p\n", data);
+ btmtk_usb_cap_init(data);
+
+ if(flag == 1){
+ ret = Ldbtusb_getBtWakeT(data);
+ os_kfree(data->woble_setting_file_name);
+ os_kfree(data->rom_patch_bin_file_name);
+ os_kfree(data->io_buf);
+ os_kfree(data);
+ return ret;
+ }
+
+ /* load bt.cfg */
+ btmtk_usb_load_bt_cfg(BT_CFG_NAME, data);
+
+ if (data->need_load_rom_patch) {
+ if (is_mt7668(data) || is_mt7663(data))
+ err = btmtk_usb_load_rom_patch_7668(data);
+ else if (is_mt7961(data))
+ err = btmtk_load_rom_patch_79xx(data);
+ else
+ err = btmtk_usb_load_rom_patch(data);
+ //btmtk_usb_send_hci_suspend_cmd(data);
+ if (err < 0) {
+ if (data->io_buf) os_kfree(data->io_buf);
+ if (data->rom_patch_bin_file_name) os_kfree(data->rom_patch_bin_file_name);
+ if (data->woble_setting_file_name) os_kfree(data->woble_setting_file_name);
+ os_kfree(data);
+ usb_debug("[ERR] end Error 2\n");
+ return err;
+ }
+ }
+
+ // Clean & free buffer
+ if (data->rom_patch_bin_file_name)
+ os_kfree(data->rom_patch_bin_file_name);
+
+
+ isUsbDisconnet = 0;
+ BT_INST(dev)->priv_data = data;
+ isbtready = 1;
+
+ //usb_debug("btmtk_usb_probe end\n");
+ return 0;
+}
+
+void LD_btmtk_usb_disconnect(mtkbt_dev_t *dev)
+{
+ struct LD_btmtk_usb_data *data = BT_INST(dev)->priv_data;
+
+ usb_debug("\n");
+
+ if (!data)
+ return;
+
+ isbtready = 0;
+ metaCount = 0;
+
+ if (data->need_load_rom_patch)
+ os_kfree(data->rom_patch);
+
+ if (data->need_load_fw)
+ os_kfree(data->fw_image);
+
+ usb_debug("unregister bt irq\n");
+
+ isUsbDisconnet = 1;
+ usb_debug("btmtk: stop all URB\n");
+ os_kfree(data->io_buf);
+ os_kfree(data);
+}
+
diff --git a/uboot_driver/Mstar/src/mtk-bt/LD_usbbt.c b/uboot_driver/Mstar/src/mtk-bt/LD_usbbt.c
new file mode 100644
index 0000000..04715f0
--- /dev/null
+++ b/uboot_driver/Mstar/src/mtk-bt/LD_usbbt.c
@@ -0,0 +1,605 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/******************************************************************************
+ *
+ * This file is provided under a dual license. When you use or
+ * distribute this software, you may choose to be licensed under
+ * version 2 of the GNU General Public License ("GPLv2 License")
+ * or BSD License.
+ *
+ * GPLv2 License
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(C) 2019 MediaTek Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+#include <command.h>
+#include <common.h>
+#include <ShareType.h>
+#include <CusConfig.h>
+#include <MsVfs.h>
+#include <MsDebug.h>
+#include <usb.h>
+#include <MsSystem.h>
+#include <stdio.h>
+#include <mtk-bt/LD_usbbt.h>
+#include <mtk-bt/LD_btmtk_usb.h>
+
+#define MAX_ROOT_PORTS 8
+
+usb_vid_pid array_mtk_vid_pid[] = {
+ {0x0E8D, 0x7668, "MTK7668"}, // 7668
+ {0x0E8D, 0x76A0, "MTK7662T"}, // 7662T
+ {0x0E8D, 0x76A1, "MTK7632T"}, // 7632T
+ {0x0E8D, 0x7663, "MTK7663"}, //7663
+ {0x0E8D, 0x7961, "MTK7961"}, //7961
+};
+
+int max_mtk_wifi_id = (sizeof(array_mtk_vid_pid) / sizeof(array_mtk_vid_pid[0]));
+usb_vid_pid *pmtk_wifi = &array_mtk_vid_pid[0];
+
+static mtkbt_dev_t *g_DrvData = NULL;
+
+VOID *os_memcpy(VOID *dst, const VOID *src, UINT32 len)
+{
+ return memcpy(dst, src, len);
+}
+
+VOID *os_memmove(VOID *dest, const void *src,UINT32 len)
+{
+ return memmove(dest, src, len);
+}
+
+VOID *os_memset(VOID *s, int c, size_t n)
+{
+ return memset(s,c,n);
+}
+
+VOID *os_kzalloc(size_t size, unsigned int flags)
+{
+ VOID *ptr = malloc(size);
+ if (ptr == NULL) {
+ usb_debug("malloc is fail, ptr is %p\n", ptr);
+ return ptr;
+ }
+
+ os_memset(ptr, 0, size);
+ return ptr;
+}
+
+int LD_load_code(unsigned char **image, char *partition, char *file, mtkbt_dev_t *dev, u32 *code_len)
+{
+ if (vfs_mount(partition) != 0) {
+ usb_debug("vfs_mount %s fail\n", partition);
+ return -1;
+ }
+
+ *code_len = vfs_getsize(file);
+ if (*code_len == 0) {
+ usb_debug("Get file size fail\n");
+ return -1;
+ }
+
+ // malloc buffer to store bt patch file data
+ *image = malloc(*code_len);
+ if (*image == NULL) {
+ usb_debug("malloc fail\n");
+ *code_len = 0;
+ return -1;
+ }
+
+ if (vfs_read(*image, file, 0, *code_len) != 0) {
+ usb_debug("vfs_read %s fail\n", file);
+ free(*image);
+ *image = NULL;
+ *code_len = 0;
+ return -1;
+ }
+
+ UBOOT_DEBUG("Load file(%s:%s) OK\n", partition, file);
+ UBOOT_DUMP((unsigned int)*image, 0x200);
+ return 0;
+}
+
+void LD_load_code_from_bin(unsigned char **image, char *bin_name, char *path, mtkbt_dev_t *dev, u32 *code_len)
+{
+ #define ENV_BT_FW_PATH "BTFWBinPath"
+ #define PARTION_NUM 6
+
+ char mtk_patch_bin_patch[128] = "\0";
+ char *bt_env;
+ char *partition[PARTION_NUM] = {"cusdata", "tvconfig", "vendor", "userdata", "system", "APP"};
+ int i = 0;
+
+ /** implement by mstar/MTK
+ * path: /system/etc/firmware/mt76XX_patch_eX_hdr.bin
+ * If argument "path" is NULL, access "/etc/firmware" directly like as request_firmware
+ * if argument "path" is not NULL, so far only support directory "userdata"
+ * NOTE: latest vfs_mount seems decided this time access directory
+ */
+ if (path) {
+ (void)snprintf(mtk_patch_bin_patch, sizeof(mtk_patch_bin_patch), "%s/%s", path, bin_name);
+ printf("File: %s\n", mtk_patch_bin_patch);
+ } else {
+#if (ENABLE_MODULE_ANDROID_BOOT == 1)
+ (void)snprintf(mtk_patch_bin_patch, sizeof(mtk_patch_bin_patch), "%s/%s", "/firmware", bin_name);
+#else
+ (void)snprintf(mtk_patch_bin_patch, sizeof(mtk_patch_bin_patch), "%s/%s", "/krl/wifi/ralink/firmware", bin_name);
+#endif
+ printf("mtk_patch_bin_patch: %s\n", mtk_patch_bin_patch);
+ }
+
+ bt_env = getenv(ENV_BT_FW_PATH);
+ if (bt_env == NULL) {
+ /* get PATH failed */
+ printf("bt_env is NULL\n");
+ for (i = 0; i < PARTION_NUM; i++) {
+ if (LD_load_code(image, partition[i], mtk_patch_bin_patch, dev, code_len) == 0)
+ return;
+ }
+ } else {
+ printf("bt_env: %s\n", bt_env);
+ LD_load_code(image, bt_env, mtk_patch_bin_patch, dev, code_len);
+ }
+
+ return;
+}
+
+static int usb_bt_bulk_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 *data,
+ int size,
+ int* realsize,
+ int timeout /* not used */
+)
+{
+ int ret =0 ;
+ if(dev == NULL || dev->udev == NULL || dev->bulk_tx_ep == NULL)
+ {
+ usb_debug("bulk out error 00\n");
+ return -1;
+ }
+
+ if(epType == MTKBT_BULK_TX_EP)
+ {
+// usb_debug_raw(data, size, "%s: usb_bulk_msg:", __func__);
+ ret = usb_bulk_msg(dev->udev,usb_sndbulkpipe(dev->udev,dev->bulk_tx_ep->bEndpointAddress),data,size,realsize,2000);
+ if(ret)
+ {
+ usb_debug("bulk out error 01, ret = %d\n", ret);
+ return -1;
+ }
+
+ if(*realsize == size)
+ {
+ //usb_debug("bulk out success 01,size =0x%x\n",size);
+ return 0;
+ }
+ else
+ {
+ usb_debug("bulk out fail 02,size =0x%x,realsize =0x%x\n",size,*realsize);
+ }
+ }
+ return -1;
+}
+
+static int usb_bt_control_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 request,
+ u8 requesttype,
+ u16 value,
+ u16 index,
+ u8 *data,
+ int data_length,
+ int timeout /* not used */
+)
+{
+ int ret = -1;
+ if(epType == MTKBT_CTRL_TX_EP)
+ {
+// usb_debug_raw(data, data_length, "%s: usb_control_msg:", __func__);
+ ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), request,
+ requesttype, value, index, data, data_length,timeout);
+ }
+ else if (epType == MTKBT_CTRL_RX_EP)
+ {
+ ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), request,
+ requesttype, value, index, data, data_length,timeout);
+ }
+ else
+ {
+ usb_debug("control message wrong Type =0x%x\n",epType);
+ }
+
+ if (ret < 0)
+ {
+ usb_debug("Err1(%d)\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int usb_bt_interrupt_msg(
+ mtkbt_dev_t *dev,
+ u32 epType,
+ u8 *data,
+ int size,
+ int* realsize,
+ int timeout /* unit of 1ms */
+)
+{
+ int ret = -1;
+
+ usb_debug("epType = 0x%x\n",epType);
+
+ if(epType == MTKBT_INTR_EP)
+ {
+ ret = usb_submit_int_msg(dev->udev,usb_rcvintpipe(dev->udev,dev->intr_ep->bEndpointAddress),data,size,realsize,timeout);
+ }
+
+ if(ret < 0 )
+ {
+ usb_debug("Err1(%d)\n", ret);
+ return ret;
+ }
+ usb_debug("ret = 0x%x\n",ret);
+ return ret;
+}
+
+static HC_IF usbbt_host_interface =
+{
+ usb_bt_bulk_msg,
+ usb_bt_control_msg,
+ usb_bt_interrupt_msg,
+};
+
+static void Ldbtusb_diconnect (btusbdev_t *dev)
+{
+ LD_btmtk_usb_disconnect(g_DrvData);
+
+ if(g_DrvData)
+ {
+ os_kfree(g_DrvData);
+ }
+ g_DrvData = NULL;
+}
+
+static int Ldbtusb_SetWoble(btusbdev_t *dev)
+{
+ if(!g_DrvData)
+ {
+ usb_debug("usb set woble fail ,because no drv data\n");
+ return -1;
+ }
+ else
+ {
+ LD_btmtk_usb_SetWoble(g_DrvData);
+ usb_debug("usb set woble end\n");
+ }
+ return 0;
+}
+
+static u32 chipid;
+
+int Ldbtusb_connect (btusbdev_t *dev, int flag)
+{
+ int ret = 0;
+
+ // For Mstar
+ struct usb_endpoint_descriptor *ep_desc;
+ struct usb_interface *iface;
+ int i;
+ iface = &dev->config.if_desc[0];
+
+ if(g_DrvData == NULL)
+ {
+ g_DrvData = os_kmalloc(sizeof(mtkbt_dev_t),MTK_GFP_ATOMIC);
+
+ if(!g_DrvData)
+ {
+ usb_debug("Not enough memory for mtkbt virtual usb device.\n");
+ return -1;
+ }
+ else
+ {
+ os_memset(g_DrvData,0,sizeof(mtkbt_dev_t));
+ g_DrvData->udev = dev;
+ g_DrvData->connect = Ldbtusb_connect;
+ g_DrvData->disconnect = Ldbtusb_diconnect;
+ g_DrvData->SetWoble = Ldbtusb_SetWoble;
+ }
+ }
+ else
+ {
+ return -1;
+ }
+
+ // For Mstar
+ for (i = 0; i < iface->desc.bNumEndpoints; i++)
+ {
+ ep_desc = &iface->ep_desc[i];
+ usb_debug("dev->endpoints[%d].bmAttributes = 0x%x\n", i, ep_desc->bmAttributes);
+ usb_debug("dev->endpoints[%d].bEndpointAddress = 0x%x\n", i, ep_desc->bEndpointAddress);
+
+ if ((ep_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
+ {
+ if (ep_desc->bEndpointAddress & USB_DIR_IN)
+ {
+ usb_debug("set endpoints[%d] to bulk_rx_ep\n", i);
+ g_DrvData->bulk_rx_ep = ep_desc;
+ }
+ else
+ {
+ if (ep_desc->bEndpointAddress != 0x1) {
+ usb_debug("set endpoints[%d] to bulk_tx_ep\n", i);
+ g_DrvData->bulk_tx_ep = ep_desc;
+ }
+ }
+ continue;
+ }
+
+ /* is it an interrupt endpoint? */
+ if (((ep_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+ && ep_desc->bEndpointAddress != 0x8f)
+ {
+ usb_debug("set endpoints[%d] to intr_ep\n", i);
+ g_DrvData->intr_ep = ep_desc;
+ continue;
+ }
+ }
+ if (!g_DrvData->intr_ep || !g_DrvData->bulk_tx_ep || !g_DrvData->bulk_rx_ep)
+ {
+ os_kfree(g_DrvData);
+ g_DrvData = NULL;
+ usb_debug("btmtk_usb_probe end Error 3\n");
+ return -1;
+ }
+
+ /* Init HostController interface */
+ g_DrvData->hci_if = &usbbt_host_interface;
+ g_DrvData->chipid = chipid;
+
+ /* btmtk init */
+ ret = LD_btmtk_usb_probe(g_DrvData, flag);
+
+ if (ret != 0)
+ {
+ usb_debug("usb probe fail\n");
+ if(g_DrvData)
+ {
+ os_kfree(g_DrvData);
+ }
+ g_DrvData = NULL;
+ return -1;
+ }
+ else
+ {
+ usb_debug("usbbt probe success\n");
+ }
+ return ret;
+}
+
+u8 LDbtusb_getWoBTW(void)
+{
+ return LD_btmtk_usb_getWoBTW();
+}
+
+static int checkUsbDevicePort(struct usb_device *udev, usb_vid_pid *pmtk_dongle, u8 port)
+{
+ struct usb_device *pdev = NULL;
+ int i;
+ int dongleCount = 0;
+#if defined (CONFIG_USB_PREINIT)
+ usb_stop(port);
+ if (usb_post_init(port) == 0)
+#else
+ if (usb_init(port) == 0)
+#endif
+ {
+ for (i = 0; i < usb_get_dev_num(); i++) {
+ pdev = usb_get_dev_index(i); // get device
+ if (pdev != NULL) {
+ for (dongleCount = 0; dongleCount < max_mtk_wifi_id; dongleCount++) {
+ if ((pdev->descriptor.idVendor == pmtk_dongle[dongleCount].vid)
+ && (pdev->descriptor.idProduct == pmtk_dongle[dongleCount].pid)) {
+ UBOOT_TRACE("OK\n");
+ memcpy(udev, pdev, sizeof(struct usb_device));
+ chipid = pmtk_dongle[dongleCount].pid;
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+static int findUsbDevice(struct usb_device* udev)
+{
+ int ret = -1;
+ u8 idx = 0;
+ char portNumStr[10] = "\0";
+ char* pBTUsbPort = NULL;
+ UBOOT_TRACE("IN\n");
+ if(udev == NULL)
+ {
+ UBOOT_ERROR("udev can not be NULL\n");
+ return -1;
+ }
+ #define BT_USB_PORT "bt_usb_port"
+ pBTUsbPort = getenv(BT_USB_PORT);
+ if(pBTUsbPort != NULL)
+ {
+ // search mtk bt usb port
+ idx = atoi(pBTUsbPort);
+ usb_debug("find mtk bt usb device from usb prot[%d]\n", idx);
+ ret = checkUsbDevicePort(udev, pmtk_wifi, idx);
+ if(ret == 0)
+ {
+ return 0;
+ }
+ }
+
+ // not find mt bt usb device from given usb port, so poll every usb port.
+ #if defined(ENABLE_FIFTH_EHC)
+ const char u8UsbPortCount = 5;
+ #elif defined(ENABLE_FOURTH_EHC)
+ const char u8UsbPortCount = 4;
+ #elif defined(ENABLE_THIRD_EHC)
+ const char u8UsbPortCount = 3;
+ #elif defined(ENABLE_SECOND_EHC)
+ const char u8UsbPortCount = 2;
+ #else
+ const char u8UsbPortCount = 1;
+ #endif
+ for(idx = 0; idx < u8UsbPortCount; idx++)
+ {
+ ret = checkUsbDevicePort(udev, pmtk_wifi, idx);
+ if(ret == 0)
+ {
+ // set bt_usb_port to store mt bt usb device port
+ (void)snprintf(portNumStr, sizeof(portNumStr), "%d", idx);
+ setenv(BT_USB_PORT, portNumStr);
+ saveenv();
+ return 0;
+ }
+ }
+ if(pBTUsbPort != NULL)
+ {
+ // env BT_USB_PORT is involid, so delete it
+ setenv(BT_USB_PORT, NULL);
+ saveenv();
+ }
+ UBOOT_ERROR("Not find usb device\n");
+ return -1;
+}
+
+int do_setMtkBT(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+ int ret = 0;
+ char* pBTUsbPort = NULL;
+ int usbPort = 0;
+ struct usb_device udev;
+ memset(&udev, 0, sizeof(struct usb_device));
+ UBOOT_TRACE("IN\n");
+ if (argc < 1)
+ {
+ cmd_usage(cmdtp);
+ return -1;
+ }
+ // MTK USB controller
+ ret = findUsbDevice(&udev);
+ if (ret != 0)
+ {
+ UBOOT_ERROR("find bt usb device failed\n");
+ return -1;
+ }
+ ret = Ldbtusb_connect(&udev, 0);
+ if(ret != 0){
+ UBOOT_ERROR("connect to bt usb device failed\n");
+ return -1;
+ }
+ ret = Ldbtusb_SetWoble(&udev);
+ if(ret != 0)
+ {
+ UBOOT_ERROR("set bt usb device woble cmd failed\n");
+ return -1;
+ }
+
+ usb_debug("ready to do usb_stop\n");
+
+ pBTUsbPort = getenv(BT_USB_PORT);
+ if(pBTUsbPort != NULL)
+ {
+ // search mtk bt usb port
+ usbPort = atoi(pBTUsbPort);
+ if (usbPort < 0 || usbPort >= MAX_ROOT_PORTS) {
+ UBOOT_ERROR("usbPort(%d) is not in correct scope\n", usbPort);
+ return -1;
+ }
+ usb_debug("stop usb port: %d\n",usbPort);
+ if(usb_stop(usbPort) != 0){
+ usb_debug("usb_stop fail\n");
+ }
+ }else{
+ usb_debug("no BT_USB_PORT\n");
+ }
+
+ UBOOT_TRACE("OK\n");
+ return ret;
+}
+
+int do_getMtkBTWakeT(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+ int ret = 0;
+ struct usb_device udev;
+ memset(&udev, 0, sizeof(struct usb_device));
+ UBOOT_TRACE("IN\n");
+ if (argc < 1)
+ {
+ cmd_usage(cmdtp);
+ return -1;
+ }
+ // MTK USB controller
+ ret = findUsbDevice(&udev);
+ if (ret != 0)
+ {
+ UBOOT_ERROR("find bt usb device failed\n");
+ return -1;
+ }
+ ret = Ldbtusb_connect(&udev, 1);
+ if(ret != 0)
+ {
+ UBOOT_ERROR("connect to bt usb device failed\n");
+ return -1;
+ }
+
+ if(ret != 0)
+ {
+ UBOOT_ERROR("set bt usb device woble cmd failed\n");
+ return -1;
+ }
+ UBOOT_TRACE("OK\n");
+ return ret;
+}
+
diff --git a/usb/btmtkusb.c b/usb/btmtkusb.c
new file mode 100644
index 0000000..ccdeedd
--- /dev/null
+++ b/usb/btmtkusb.c
@@ -0,0 +1,3587 @@
+/*
+ *
+ * Generic Bluetooth USB driver
+ *
+ * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/usb/quirks.h>
+#include <linux/firmware.h>
+#include <asm/unaligned.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btmtk_usb.h"
+
+static struct usb_driver btusb_driver;
+static struct btmtk_cif_chip_reset reset_func;
+static int intf_to_idx[BT_MCU_INTERFACE_NUM_MAX] = {0, -1, -1, 1};
+static struct btmtk_usb_dev g_usb_dev[BT_MCU_MINIMUM_INTERFACE_NUM][BT_MCU_NUM_MAX];
+
+static const struct usb_device_id btusb_table[] = {
+ /* Mediatek MT7961 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7961, 0xe0, 0x01, 0x01) },
+ /* Mediatek MT7915 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7915, 0xe0, 0x01, 0x01) },
+ /* Mediatek MT7922 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7922, 0xe0, 0x01, 0x01) },
+ /* Mediatek MT7902 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7902, 0xe0, 0x01, 0x01) },
+ /* Mediatek MT6639 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x6639, 0xe0, 0x01, 0x01) },
+ { } /* Terminating entry */
+};
+
+static u8 wmt_trigger_assert[WMT_TRIGGER_ASSERT_LEN] = { 0x01, 0x6f, 0xfc, 0x05,
+ 0x01, 0x02, 0x01, 0x00, 0x08 };
+static char event_need_compare[EVENT_COMPARE_SIZE] = {0};
+static char event_need_compare_len;
+static char event_compare_status;
+
+static DEFINE_MUTEX(btmtk_usb_ops_mutex);
+#define USB_OPS_MUTEX_LOCK() mutex_lock(&btmtk_usb_ops_mutex)
+#define USB_OPS_MUTEX_UNLOCK() mutex_unlock(&btmtk_usb_ops_mutex)
+
+MODULE_DEVICE_TABLE(usb, btusb_table);
+
+/* remove #define BTUSB_MAX_ISOC_FRAMES 10
+ * ISCO_FRAMES max is 24
+ */
+#define BTUSB_MAX_ISOC_FRAMES 24
+
+#define BTUSB_INTR_RUNNING 0
+#define BTUSB_BULK_RUNNING 1
+#define BTUSB_ISOC_RUNNING 2
+#define BTUSB_SUSPENDING 3
+#define BTUSB_DID_ISO_RESUME 4
+#define BTUSB_BLE_ISOC_RUNNING 5
+#define BTUSB_WMT_RUNNING 6
+
+#define DEVICE_VENDOR_REQUEST_IN 0xc0
+#define DEVICE_CLASS_REQUEST_OUT 0x20
+#define USB_CTRL_IO_TIMO 100
+
+#define BTMTK_IS_BT_0_INTF(ifnum_base) \
+ (ifnum_base == BT0_MCU_INTERFACE_NUM)
+
+#define BTMTK_IS_BT_1_INTF(ifnum_base) \
+ (ifnum_base == BT1_MCU_INTERFACE_NUM)
+
+#define BTMTK_CIF_GET_DEV_PRIV(bdev, intf, ifnum_base) \
+ do { \
+ bdev = usb_get_intfdata(intf); \
+ ifnum_base = intf->cur_altsetting->desc.bInterfaceNumber; \
+ } while (0)
+
+static int btmtk_cif_allocate_memory(struct btmtk_usb_dev *cif_dev);
+static void btmtk_cif_free_memory(struct btmtk_usb_dev *cif_dev);
+static int btmtk_cif_write_uhw_register(struct btmtk_dev *bdev, u32 reg, u32 val);
+static int btmtk_cif_read_uhw_register(struct btmtk_dev *bdev, u32 reg, u32 *val);
+
+static int btmtk_usb_send_and_recv(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type);
+static void btmtk_usb_chip_reset_notify(struct btmtk_dev *bdev);
+static int btmtk_usb_event_filter(struct btmtk_dev *bdev, struct sk_buff *skb);
+static int btmtk_usb_send_cmd(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type);
+static int btmtk_usb_read_register(struct btmtk_dev *bdev, u32 reg, u32 *val);
+static int btmtk_usb_write_register(struct btmtk_dev *bdev, u32 reg, u32 val);
+
+static void btmtk_usb_dump_debug_register(struct btmtk_dev *bdev,
+ struct debug_reg_struct debug_reg)
+{
+ struct btmtk_sdio_dev *cif_dev = NULL;
+ u32 value = 0, i = 0, count = 0;
+ static u32 reg_page[] = {0, 0};
+
+ cif_dev = (struct btmtk_sdio_dev *)bdev->cif_dev;
+
+ count = debug_reg.num;
+ for (i = 0; i < count; i++) {
+ if (!debug_reg.reg[i].length)
+ continue;
+
+ switch (debug_reg.reg[i].length) {
+ case 1:
+ /* reg read address */
+ btmtk_cif_read_uhw_register(bdev,
+ debug_reg.reg[i].content[0], &value);
+ BTMTK_INFO("%s R(0x%08X) = 0x%08X",
+ __func__,
+ debug_reg.reg[i].content[0], value);
+ break;
+ case 2:
+ /* write reg address and value */
+ btmtk_cif_write_uhw_register(bdev, debug_reg.reg[i].content[0],
+ debug_reg.reg[i].content[1]);
+ reg_page[0] = debug_reg.reg[i].content[0];
+ reg_page[1] = debug_reg.reg[i].content[1];
+ BTMTK_INFO("%s W(0x%08X) = 0x%08X",
+ __func__,
+ debug_reg.reg[i].content[0], debug_reg.reg[i].content[1]);
+ break;
+ case 3:
+ /* write reg and read reg */
+ btmtk_cif_write_uhw_register(bdev, debug_reg.reg[i].content[0],
+ debug_reg.reg[i].content[1]);
+ btmtk_cif_read_uhw_register(bdev,
+ debug_reg.reg[i].content[2], &value);
+ BTMTK_INFO("%s W(0x%08X) = 0x%08X, W(0x%08X) = 0x%08X, R(0x%08X) = 0x%08X",
+ __func__,
+ reg_page[0], reg_page[1],
+ debug_reg.reg[i].content[0], debug_reg.reg[i].content[1],
+ debug_reg.reg[i].content[2], value);
+ break;
+ default:
+ BTMTK_WARN("%s: Unknown result: %d", __func__, debug_reg.reg[i].length);
+ break;
+ }
+ }
+}
+
+static void btmtk_usb_dump_debug_sop(struct btmtk_dev *bdev)
+{
+ /* dump mcu_sleep_wakeup_debug(BGFSYS_status),
+ * only for PCIE, USB/SDIO not support
+ */
+ if (bdev == NULL) {
+ BTMTK_ERR("%s bdev is NULL", __func__);
+ return;
+ }
+
+ BTMTK_INFO("%s -debug sop dump start", __func__);
+ btmtk_usb_dump_debug_register(bdev, bdev->debug_sop_reg_dump);
+ BTMTK_INFO("%s -debug sop dump end", __func__);
+}
+
+static void btmtk_usb_cif_mutex_lock(struct btmtk_dev *bdev)
+{
+ USB_OPS_MUTEX_LOCK();
+}
+
+static void btmtk_usb_cif_mutex_unlock(struct btmtk_dev *bdev)
+{
+ USB_OPS_MUTEX_UNLOCK();
+}
+
+static inline void btusb_free_frags(struct btmtk_dev *bdev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bdev->rxlock, flags);
+
+ kfree_skb(bdev->evt_skb);
+ bdev->evt_skb = NULL;
+
+ kfree_skb(bdev->sco_skb);
+ bdev->sco_skb = NULL;
+
+ spin_unlock_irqrestore(&bdev->rxlock, flags);
+}
+
+static int btusb_recv_isoc(struct btmtk_dev *bdev, void *buffer, int count)
+{
+ struct sk_buff *skb;
+ int err = 0;
+ unsigned char *skb_tmp = NULL;
+
+ spin_lock(&bdev->rxlock);
+ skb = bdev->sco_skb;
+
+ while (count) {
+ int len;
+
+ if (!skb) {
+ skb = bt_skb_alloc(HCI_MAX_SCO_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ err = -ENOMEM;
+ break;
+ }
+
+ hci_skb_pkt_type(skb) = HCI_SCODATA_PKT;
+ hci_skb_expect(skb) = HCI_SCO_HDR_SIZE;
+ }
+
+ len = min_t(uint, hci_skb_expect(skb), count);
+ skb_tmp = skb_put(skb, len);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed. Len = %d!", __func__, len);
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ memcpy(skb_tmp, buffer, len);
+
+ count -= len;
+ buffer += len;
+ hci_skb_expect(skb) -= len;
+
+ if (skb->len == HCI_SCO_HDR_SIZE) {
+ /* Complete SCO header */
+ hci_skb_expect(skb) = hci_sco_hdr(skb)->dlen;
+
+ if (skb_tailroom(skb) < hci_skb_expect(skb)) {
+ kfree_skb(skb);
+ skb = NULL;
+
+ err = -EILSEQ;
+ break;
+ }
+ }
+
+ if (!hci_skb_expect(skb)) {
+ /* Complete frame */
+ hci_recv_frame(bdev->hdev, skb);
+ skb = NULL;
+ }
+ }
+
+ bdev->sco_skb = skb;
+ spin_unlock(&bdev->rxlock);
+
+ return err;
+}
+
+static void btusb_intr_complete(struct urb *urb)
+{
+ struct hci_dev *hdev = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int err;
+ u8 *buf;
+ static u8 intr_blocking_usb_warn;
+
+ if (urb == NULL) {
+ BTMTK_ERR("%s: ERROR, urb is NULL!", __func__);
+ return;
+ }
+
+ hdev = urb->context;
+ if (hdev == NULL) {
+ BTMTK_ERR("%s: ERROR, hdev is NULL!", __func__);
+ return;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: ERROR, bdev is NULL!", __func__);
+ return;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("%s: ERROR, cif_dev is NULL!", __func__);
+ return;
+ }
+
+ BTMTK_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status,
+ urb->actual_length);
+
+ if (urb->status != 0 && intr_blocking_usb_warn < 10) {
+ intr_blocking_usb_warn++;
+ BTMTK_WARN("%s: urb %p urb->status %d count %d", __func__,
+ urb, urb->status, urb->actual_length);
+ } else if (urb->status == 0 && urb->actual_length != 0)
+ intr_blocking_usb_warn = 0;
+
+ if (urb->status == 0) {
+ buf = urb->transfer_buffer;
+ if (buf[0] == 0x3E)
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_ADV_EVT_HIF,
+ buf, urb->actual_length);
+ else if (buf[0] == 0x13)
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_NOCP_EVT_HIF,
+ buf, urb->actual_length);
+ else
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_EVT_HIF,
+ buf, urb->actual_length);
+
+ hdev->stat.byte_rx += urb->actual_length;
+
+ if (!cif_dev->urb_intr_buf) {
+ BTMTK_ERR("%s: bdev->urb_intr_buf is NULL!", __func__);
+ return;
+ }
+
+ if (urb->actual_length >= URB_MAX_BUFFER_SIZE ||
+ (urb->actual_length != get_pkt_len(HCI_EVENT_PKT, buf) && urb->actual_length > 1)) {
+ BTMTK_ERR("%s: urb->actual_length is invalid, buf[1] = %d!",
+ __func__, buf[1]);
+ btmtk_hci_snoop_print(urb->transfer_buffer, urb->actual_length);
+ goto intr_resub;
+ }
+ memset(cif_dev->urb_intr_buf, 0, URB_MAX_BUFFER_SIZE);
+ cif_dev->urb_intr_buf[0] = HCI_EVENT_PKT;
+ memcpy(cif_dev->urb_intr_buf + 1, urb->transfer_buffer, urb->actual_length);
+
+ BTMTK_DBG("%s ,urb->actual_length = %d", __func__, urb->actual_length);
+ BTMTK_DBG_RAW(cif_dev->urb_intr_buf, urb->actual_length + 1, "%s, recv evt", __func__);
+ BTMTK_DBG_RAW(urb->transfer_buffer, urb->actual_length, "%s, recv evt", __func__);
+ if (cif_dev->urb_intr_buf[1] == 0xFF && urb->actual_length == 1) {
+ /* We can't use usb_control_msg in interrupt.
+ * If you use usb_control_msg , it will cause crash.
+ * Receive a bytes 0xFF from controller, it's WDT interrupt to driver.
+ * WDT interrupt is a mechanism to do L0.5 reset.
+ */
+ DUMP_TIME_STAMP("notify_chip_reset");
+ btmtk_reset_trigger(bdev);
+ goto intr_resub;
+ }
+
+ err = btmtk_recv(hdev, cif_dev->urb_intr_buf, urb->actual_length + 1);
+ if (err) {
+ BTMTK_ERR("%s corrupted event packet, urb_intr_buf = %p, transfer_buffer = %p",
+ hdev->name, cif_dev->urb_intr_buf, urb->transfer_buffer);
+ btmtk_hci_snoop_print(urb->transfer_buffer, urb->actual_length);
+ btmtk_hci_snoop_print(cif_dev->urb_intr_buf, urb->actual_length + 1);
+ hdev->stat.err_rx++;
+ }
+ } else if (urb->status == -ENOENT) {
+ BTMTK_INFO("%s: urb->status is ENOENT!", __func__);
+ return;
+ }
+
+intr_resub:
+ if (!test_bit(BTUSB_INTR_RUNNING, &bdev->flags)) {
+ BTMTK_INFO("%s: test_bit is not running!", __func__);
+ return;
+ }
+
+ usb_mark_last_busy(cif_dev->udev);
+ usb_anchor_urb(urb, &cif_dev->intr_anchor);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ /* -EPERM: urb is being killed;
+ * -ENODEV: device got disconnected
+ */
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p failed to resubmit (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+}
+
+static int btusb_submit_intr_reset_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size;
+ struct btmtk_usb_dev *cif_dev = NULL;
+
+ /* If WDT reset happened, fw will send a bytes (FF) to host */
+ BTMTK_DBG("%s", hdev->name);
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (!cif_dev->reset_intr_ep)
+ return -ENODEV;
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+ /* Default size is 16 */
+ /* size = le16_to_cpu(data->intr_ep->wMaxPacketSize); */
+ /* Buzzard Endpoint description.
+ * bEndpointAddress 0x8f EP 15 IN
+ * wMaxPacketSize 0x0001 1x 1 bytes
+ */
+ size = le16_to_cpu(HCI_MAX_EVENT_SIZE);
+
+ buf = kmalloc(size, mem_flags);
+ if (!buf) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvintpipe(cif_dev->udev, cif_dev->reset_intr_ep->bEndpointAddress);
+
+ /* fw issue, we need to submit urb with a byte
+ * If driver set size = le16_to_cpu(HCI_MAX_EVENT_SIZE) to usb_fill_int_urb
+ * We can't get interrupt callback from bus.
+ */
+ usb_fill_int_urb(urb, cif_dev->udev, pipe, buf, 1,
+ btusb_intr_complete, hdev, cif_dev->reset_intr_ep->bInterval);
+
+ urb->transfer_flags |= URB_FREE_BUFFER;
+
+ usb_anchor_urb(urb, &cif_dev->intr_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static void btusb_mtk_wmt_recv(struct urb *urb)
+{
+ struct hci_dev *hdev = urb->context;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct sk_buff *skb;
+ unsigned char *skb_tmp = NULL;
+ int err;
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ BTMTK_DBG("%s : %s urb %p status %d count %d", __func__, hdev->name, urb, urb->status,
+ urb->actual_length);
+
+ if (urb->status == 0 && urb->actual_length > 0) {
+ BTMTK_DBG_RAW(urb->transfer_buffer, urb->actual_length, "%s, recv evt", __func__);
+ hdev->stat.byte_rx += urb->actual_length;
+ skb = bt_skb_alloc(HCI_MAX_EVENT_SIZE, GFP_ATOMIC);
+ if (!skb) {
+ BTMTK_ERR("%s skb is null!", __func__);
+ hdev->stat.err_rx++;
+ goto exit;
+ }
+
+ if (urb->actual_length >= HCI_MAX_EVENT_SIZE) {
+ BTMTK_ERR("%s urb->actual_length is invalid!", __func__);
+ BTMTK_INFO_RAW(urb->transfer_buffer, urb->actual_length,
+ "urb->actual_length:%d, urb->transfer_buffer:%p",
+ urb->actual_length, urb->transfer_buffer);
+ kfree_skb(skb);
+ hdev->stat.err_rx++;
+ goto exit;
+ }
+ hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
+ skb_tmp = skb_put(skb, urb->actual_length);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed!", __func__);
+ kfree_skb(skb);
+ return;
+ }
+ memcpy(skb_tmp, urb->transfer_buffer, urb->actual_length);
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, skb recv evt", __func__);
+
+ hci_recv_frame(hdev, skb);
+ return;
+ } else if (urb->status == -ENOENT) {
+ /* it's error case, don't re-submit urb. */
+ BTMTK_INFO("%s: urb->status is ENOENT!", __func__);
+ return;
+ }
+
+ usb_mark_last_busy(cif_dev->udev);
+ /* The URB complete handler is still called with urb->actual_length = 0
+ * when the event is not available, so we should keep re-submitting
+ * URB until WMT event returns, Also, It's necessary to wait some time
+ * between the two consecutive control URBs to relax the target device
+ * to generate the event. Otherwise, the WMT event cannot return from
+ * the device successfully.
+ */
+ udelay(100);
+
+ if (!test_bit(BTUSB_WMT_RUNNING, &bdev->flags)) {
+ BTMTK_INFO("%s test flag failed", __func__);
+ goto exit;
+ }
+
+ usb_anchor_urb(urb, &cif_dev->ctrl_anchor);
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ kfree(urb->setup_packet);
+ /* -EPERM: urb is being killed;
+ * -ENODEV: device got disconnected
+ */
+ if (err != -EPERM && err != -ENODEV)
+ usb_unanchor_urb(urb);
+ }
+
+ return;
+
+exit:
+ kfree(urb->setup_packet);
+}
+
+static int btusb_submit_wmt_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct usb_ctrlrequest *dr;
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size;
+ unsigned int ifnum_base;
+
+ BTMTK_DBG("%s : %s", __func__, hdev->name);
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+
+ size = le16_to_cpu(HCI_MAX_EVENT_SIZE);
+
+ dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+ if (!dr) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (BTMTK_IS_BT_0_INTF(ifnum_base)) {
+ dr->bRequestType = 0xC0;
+ dr->bRequest = 0x01;
+ dr->wIndex = 0;
+ dr->wValue = 0x30;
+ dr->wLength = __cpu_to_le16(size);
+ } else if (BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ dr->bRequestType = 0xA1;
+ dr->bRequest = 0x01;
+ dr->wIndex = 0x03;
+ dr->wValue = 0x30;
+ dr->wLength = __cpu_to_le16(size);
+ }
+
+ pipe = usb_rcvctrlpipe(cif_dev->udev, 0);
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf) {
+ kfree(dr);
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ usb_fill_control_urb(urb, cif_dev->udev, pipe, (void *)dr,
+ buf, size, btusb_mtk_wmt_recv, hdev);
+
+ urb->transfer_flags |= URB_FREE_BUFFER;
+
+ usb_anchor_urb(urb, &cif_dev->ctrl_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ kfree(dr);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size;
+
+ BTMTK_DBG("%s", hdev->name);
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ if (!cif_dev->intr_ep)
+ return -ENODEV;
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+
+ /* size = le16_to_cpu(data->intr_ep->wMaxPacketSize); */
+ /* Buzzard Endpoint description.
+ * bEndpointAddress 0x81 EP 1 IN
+ * wMaxPacketSize 0x0010 1x 16 bytes
+ */
+ size = le16_to_cpu(HCI_MAX_EVENT_SIZE);
+ BTMTK_INFO("%s: maximum packet size:%d", __func__, size);
+
+ buf = kmalloc(size, mem_flags);
+ if (!buf) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvintpipe(cif_dev->udev, cif_dev->intr_ep->bEndpointAddress);
+
+ usb_fill_int_urb(urb, cif_dev->udev, pipe, buf, size,
+ btusb_intr_complete, hdev, cif_dev->intr_ep->bInterval);
+
+ urb->transfer_flags |= URB_FREE_BUFFER;
+
+ usb_anchor_urb(urb, &cif_dev->intr_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static void btusb_bulk_complete(struct urb *urb)
+{
+ struct hci_dev *hdev = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int err;
+ u8 *buf;
+ u16 len = 0;
+ static u8 block_bulkin_usb_warn;
+
+ if (urb == NULL) {
+ BTMTK_ERR("%s: ERROR, urb is NULL!", __func__);
+ return;
+ }
+
+ hdev = urb->context;
+ if (hdev == NULL) {
+ BTMTK_ERR("%s: ERROR, hdev is NULL!", __func__);
+ return;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: ERROR, bdev is NULL!", __func__);
+ return;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("%s: ERROR, cif_dev is NULL!", __func__);
+ return;
+ }
+
+ if (urb->status != 0 && block_bulkin_usb_warn < 10) {
+ block_bulkin_usb_warn++;
+ BTMTK_INFO("%s: urb %p urb->status %d count %d", __func__, urb,
+ urb->status, urb->actual_length);
+ } else if (urb->status == 0 && urb->actual_length != 0)
+ block_bulkin_usb_warn = 0;
+
+ /*
+ * This flag didn't support in kernel 4.x
+ * Driver will remove it
+ * if (!test_bit(HCI_RUNNING, &hdev->flags))
+ * return;
+ */
+ if (urb->status == 0) {
+ buf = urb->transfer_buffer;
+ if ((buf[0] == 0x6f && buf[1] == 0xfc) ||
+ ((buf[0] == 0xff || buf[0] == 0xfe) && buf[1] == 0x05))
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_RX_ACL_HIF,
+ buf, urb->actual_length);
+
+ hdev->stat.byte_rx += urb->actual_length;
+ if (!cif_dev->urb_bulk_buf) {
+ BTMTK_ERR("%s: bdev->urb_bulk_buf is NULL!", __func__);
+ return;
+ }
+
+ len = get_pkt_len(HCI_ACLDATA_PKT, buf);
+ if (urb->actual_length >= URB_MAX_BUFFER_SIZE ||
+ urb->actual_length != len) {
+ BTMTK_ERR("%s urb->actual_length is invalid, len = %d!", __func__, len);
+ btmtk_hci_snoop_print(urb->transfer_buffer, urb->actual_length);
+ goto bulk_resub;
+ }
+ memset(cif_dev->urb_bulk_buf, 0, URB_MAX_BUFFER_SIZE);
+ cif_dev->urb_bulk_buf[0] = HCI_ACLDATA_PKT;
+ memcpy(cif_dev->urb_bulk_buf + 1, urb->transfer_buffer, urb->actual_length);
+
+ /* BTMTK_DBG_RAW(bdev->urb_bulk_buf, urb->actual_length + 1, "%s, recv from bulk", __func__); */
+ err = btmtk_recv(hdev, cif_dev->urb_bulk_buf, urb->actual_length + 1);
+ if (err) {
+ BTMTK_ERR("%s corrupted ACL packet, urb_bulk_buf = %p, transfer_buffer = %p",
+ hdev->name, cif_dev->urb_bulk_buf, urb->transfer_buffer);
+ btmtk_hci_snoop_print(urb->transfer_buffer, urb->actual_length);
+ btmtk_hci_snoop_print(cif_dev->urb_bulk_buf, urb->actual_length + 1);
+ hdev->stat.err_rx++;
+ }
+ } else if (urb->status == -ENOENT) {
+ /* Avoid suspend failed when usb_kill_urb */
+ BTMTK_INFO("%s urb %p status %d count %d", hdev->name, urb, urb->status,
+ urb->actual_length);
+ return;
+ }
+
+bulk_resub:
+ if (!test_bit(BTUSB_BULK_RUNNING, &bdev->flags)) {
+ BTMTK_INFO("%s test flag failed", __func__);
+ return;
+ }
+
+ usb_anchor_urb(urb, &cif_dev->bulk_anchor);
+ usb_mark_last_busy(cif_dev->udev);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ /* -EPERM: urb is being killed;
+ * -ENODEV: device got disconnected
+ */
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p failed to resubmit (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+}
+
+static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size = HCI_MAX_FRAME_SIZE;
+
+ BTMTK_DBG("%s", hdev->name);
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ if (!cif_dev->bulk_rx_ep)
+ return -ENODEV;
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+
+ buf = kmalloc(size, mem_flags);
+ if (!buf) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvbulkpipe(cif_dev->udev, cif_dev->bulk_rx_ep->bEndpointAddress);
+
+ usb_fill_bulk_urb(urb, cif_dev->udev, pipe, buf, size,
+ btusb_bulk_complete, hdev);
+
+ urb->transfer_flags |= URB_FREE_BUFFER;
+
+ usb_mark_last_busy(cif_dev->udev);
+ usb_anchor_urb(urb, &cif_dev->bulk_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static void btusb_ble_isoc_complete(struct urb *urb)
+{
+ struct hci_dev *hdev = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int err;
+ u8 *isoc_buf;
+ int isoc_pkt_len;
+
+ /*
+ * This flag didn't support in kernel 4.x
+ * Driver will remove it
+ * if (!test_bit(HCI_RUNNING, &hdev->flags))
+ * return;
+ */
+ if (urb == NULL) {
+ BTMTK_ERR("%s: ERROR, urb is NULL!", __func__);
+ return;
+ }
+
+ hdev = urb->context;
+ if (hdev == NULL) {
+ BTMTK_ERR("%s: ERROR, hdev is NULL!", __func__);
+ return;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: ERROR, bdev is NULL!", __func__);
+ return;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("%s: ERROR, cif_dev is NULL!", __func__);
+ return;
+ }
+
+ if (urb->status == 0) {
+ isoc_buf = urb->transfer_buffer;
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_RX_ISO_HIF,
+ isoc_buf, urb->actual_length);
+ hdev->stat.byte_rx += urb->actual_length;
+
+ if (!cif_dev->urb_ble_isoc_buf) {
+ BTMTK_ERR("%s: bdev->urb_ble_isoc_buf is NULL!", __func__);
+ return;
+ }
+
+ isoc_pkt_len = get_pkt_len(HCI_ISO_PKT, isoc_buf);
+ /* Skip padding */
+ BTMTK_DBG("%s: isoc_pkt_len = %d, urb->actual_length = %d", __func__, isoc_pkt_len, urb->actual_length);
+ if (isoc_pkt_len == HCI_ISO_PKT_HEADER_SIZE) {
+ BTMTK_DBG("%s: goto ble_iso_resub", __func__);
+ goto ble_iso_resub;
+ }
+
+ if (urb->actual_length + HCI_ISO_PKT_WITH_ACL_HEADER_SIZE > URB_MAX_BUFFER_SIZE) {
+ BTMTK_ERR("%s urb->actual_length is invalid!", __func__);
+ btmtk_hci_snoop_print(urb->transfer_buffer, urb->actual_length);
+ goto ble_iso_resub;
+ }
+ /* It's mtk specific heade for stack
+ * hci layered didn't support 0x05 for ble iso, it will drop the packet type with 0x05
+ * Driver will replace 0x05 to 0x02
+ * header format : 0x02 0x00 0x44 xx xx + isoc packet header & payload
+ */
+ memset(cif_dev->urb_ble_isoc_buf, 0, URB_MAX_BUFFER_SIZE);
+ cif_dev->urb_ble_isoc_buf[0] = HCI_ACLDATA_PKT;
+ cif_dev->urb_ble_isoc_buf[1] = 0x00;
+ cif_dev->urb_ble_isoc_buf[2] = 0x44;
+ cif_dev->urb_ble_isoc_buf[3] = (isoc_pkt_len & 0x00ff);
+ cif_dev->urb_ble_isoc_buf[4] = (isoc_pkt_len >> 8);
+ memcpy(cif_dev->urb_ble_isoc_buf + HCI_ISO_PKT_WITH_ACL_HEADER_SIZE,
+ urb->transfer_buffer, isoc_pkt_len);
+
+ BTMTK_DBG_RAW(cif_dev->urb_ble_isoc_buf,
+ isoc_pkt_len + HCI_ISO_PKT_WITH_ACL_HEADER_SIZE,
+ "%s: raw data is :", __func__);
+
+ err = btmtk_recv(hdev, cif_dev->urb_ble_isoc_buf,
+ isoc_pkt_len + HCI_ISO_PKT_WITH_ACL_HEADER_SIZE);
+ if (err) {
+ BTMTK_ERR("%s corrupted ACL packet", hdev->name);
+ hdev->stat.err_rx++;
+ }
+ } else if (urb->status == -ENOENT) {
+ BTMTK_INFO("%s: urb->status is ENOENT!", __func__);
+ return;
+ }
+
+ble_iso_resub:
+ if (!test_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags)) {
+ BTMTK_INFO("%s: bdev->flags is not RUNNING!", __func__);
+ return;
+ }
+
+ usb_anchor_urb(urb, &cif_dev->ble_isoc_anchor);
+ usb_mark_last_busy(cif_dev->udev);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ /* -EPERM: urb is being killed;
+ * -ENODEV: device got disconnected
+ */
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p failed to resubmit (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+}
+
+static int btusb_submit_intr_ble_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size;
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ if (!cif_dev->intr_iso_rx_ep)
+ return -ENODEV;
+
+ urb = usb_alloc_urb(0, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+ /* Default size is 16 */
+ /* size = le16_to_cpu(data->intr_ep->wMaxPacketSize); */
+ /* we need to consider the wMaxPacketSize in BLE ISO */
+ size = le16_to_cpu(2000);
+
+ buf = kmalloc(size, mem_flags);
+ if (!buf) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvintpipe(cif_dev->udev, cif_dev->intr_iso_rx_ep->bEndpointAddress);
+ BTMTK_INFO("btusb_submit_intr_iso_urb : polling 0x%02X", cif_dev->intr_iso_rx_ep->bEndpointAddress);
+
+ usb_fill_int_urb(urb, cif_dev->udev, pipe, buf, size,
+ btusb_ble_isoc_complete, hdev, cif_dev->intr_iso_rx_ep->bInterval);
+
+ urb->transfer_flags |= URB_FREE_BUFFER;
+
+ usb_anchor_urb(urb, &cif_dev->ble_isoc_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static void btusb_isoc_complete(struct urb *urb)
+{
+ struct hci_dev *hdev = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int i, err;
+
+ if (urb == NULL) {
+ BTMTK_ERR("%s: ERROR, urb is NULL!", __func__);
+ return;
+ }
+
+ hdev = urb->context;
+ if (hdev == NULL) {
+ BTMTK_ERR("%s: ERROR, hdev is NULL!", __func__);
+ return;
+ }
+
+ bdev = hci_get_drvdata(hdev);
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: ERROR, bdev is NULL!", __func__);
+ return;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (cif_dev == NULL) {
+ BTMTK_ERR("%s: ERROR, cif_dev is NULL!", __func__);
+ return;
+ }
+
+
+ BTMTK_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status,
+ urb->actual_length);
+
+ if (!test_bit(HCI_RUNNING, &hdev->flags))
+ return;
+
+ if (urb->status == 0) {
+ for (i = 0; i < urb->number_of_packets; i++) {
+ unsigned int offset = urb->iso_frame_desc[i].offset;
+ unsigned int length = urb->iso_frame_desc[i].actual_length;
+
+ if (urb->iso_frame_desc[i].status)
+ continue;
+
+ hdev->stat.byte_rx += length;
+
+ if (btusb_recv_isoc(bdev, urb->transfer_buffer + offset,
+ length) < 0) {
+ BTMTK_ERR("%s corrupted SCO packet", hdev->name);
+ hdev->stat.err_rx++;
+ }
+ }
+ } else if (urb->status == -ENOENT) {
+ BTMTK_INFO("%s: urb->status is ENOENT!", __func__);
+ return;
+ }
+
+ if (!test_bit(BTUSB_ISOC_RUNNING, &bdev->flags)) {
+ BTMTK_INFO("%s: bdev->flags is RUNNING!", __func__);
+ return;
+ }
+
+ usb_anchor_urb(urb, &cif_dev->isoc_anchor);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ /* -EPERM: urb is being killed;
+ * -ENODEV: device got disconnected
+ */
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p failed to resubmit (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+}
+
+static inline void __fill_isoc_descriptor(struct urb *urb, int len, int mtu)
+{
+ int i, offset = 0;
+
+ BTMTK_DBG("len %d mtu %d", len, mtu);
+
+ for (i = 0; i < BTUSB_MAX_ISOC_FRAMES && len >= mtu;
+ i++, offset += mtu, len -= mtu) {
+ urb->iso_frame_desc[i].offset = offset;
+ urb->iso_frame_desc[i].length = mtu;
+ }
+
+ if (len && i < BTUSB_MAX_ISOC_FRAMES) {
+ urb->iso_frame_desc[i].offset = offset;
+ urb->iso_frame_desc[i].length = len;
+ i++;
+ }
+
+ urb->number_of_packets = i;
+}
+
+static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct urb *urb;
+ unsigned char *buf;
+ unsigned int pipe;
+ int err, size;
+
+ BTMTK_DBG("%s", hdev->name);
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ if (!cif_dev->isoc_rx_ep)
+ return -ENODEV;
+
+ urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, mem_flags);
+ if (!urb)
+ return -ENOMEM;
+
+ size = le16_to_cpu(cif_dev->isoc_rx_ep->wMaxPacketSize) *
+ BTUSB_MAX_ISOC_FRAMES;
+
+ buf = kmalloc(size, mem_flags);
+ if (!buf) {
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvisocpipe(cif_dev->udev, cif_dev->isoc_rx_ep->bEndpointAddress);
+
+ usb_fill_int_urb(urb, cif_dev->udev, pipe, buf, size, btusb_isoc_complete,
+ hdev, cif_dev->isoc_rx_ep->bInterval);
+
+ urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP;
+
+ __fill_isoc_descriptor(urb, size,
+ le16_to_cpu(cif_dev->isoc_rx_ep->wMaxPacketSize));
+
+ usb_anchor_urb(urb, &cif_dev->isoc_anchor);
+
+ err = usb_submit_urb(urb, mem_flags);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ usb_unanchor_urb(urb);
+ }
+
+ usb_free_urb(urb);
+
+ return err;
+}
+
+static void btusb_tx_complete(struct urb *urb)
+{
+ struct sk_buff *skb = urb->context;
+ struct hci_dev *hdev = (struct hci_dev *)skb->dev;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ unsigned long flags;
+
+ BTMTK_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status,
+ urb->actual_length);
+
+ if (!test_bit(HCI_RUNNING, &hdev->flags))
+ goto done;
+
+ if (!urb->status)
+ hdev->stat.byte_tx += urb->transfer_buffer_length;
+ else
+ hdev->stat.err_tx++;
+
+done:
+ spin_lock_irqsave(&bdev->txlock, flags);
+ bdev->tx_in_flight--;
+ spin_unlock_irqrestore(&bdev->txlock, flags);
+
+ kfree(urb->setup_packet);
+
+ kfree_skb(skb);
+}
+
+static void btusb_isoc_tx_complete(struct urb *urb)
+{
+ struct sk_buff *skb = urb->context;
+ struct hci_dev *hdev = (struct hci_dev *)skb->dev;
+
+ BTMTK_DBG("%s urb %p status %d count %d", hdev->name, urb, urb->status,
+ urb->actual_length);
+
+ if (!test_bit(HCI_RUNNING, &hdev->flags))
+ goto done;
+
+ if (!urb->status)
+ hdev->stat.byte_tx += urb->transfer_buffer_length;
+ else
+ hdev->stat.err_tx++;
+
+done:
+ kfree(urb->setup_packet);
+
+ kfree_skb(skb);
+}
+
+static int btmtk_usb_open(struct hci_dev *hdev)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int err;
+ unsigned int ifnum_base;
+ unsigned char state = 0;
+
+ BTMTK_INFO("%s enter!", __func__);
+
+ BTMTK_DBG("%s", hdev->name);
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_SUSPEND
+ && state != BTMTK_STATE_RESUME
+ && state != BTMTK_STATE_STANDBY) {
+ err = usb_autopm_get_interface(cif_dev->intf);
+ if (err < 0)
+ return err;
+ }
+
+ cif_dev->intf->needs_remote_wakeup = 1;
+
+ if (test_and_set_bit(BTUSB_INTR_RUNNING, &bdev->flags))
+ goto done;
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+
+ if (is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id)) {
+ BTMTK_INFO("%s 7961 submit urb\n", __func__);
+ if (BTMTK_IS_BT_0_INTF(ifnum_base)) {
+ if (cif_dev->reset_intr_ep) {
+ err = btusb_submit_intr_reset_urb(hdev, GFP_KERNEL);
+ if (err < 0)
+ goto failed;
+ } else
+ BTMTK_INFO("%s, reset_intr_ep missing, don't submit_intr_reset_urb!",
+ __func__);
+
+ if (cif_dev->intr_iso_rx_ep) {
+ err = btusb_submit_intr_ble_isoc_urb(hdev, GFP_KERNEL);
+ if (err < 0) {
+ usb_kill_anchored_urbs(&cif_dev->ble_isoc_anchor);
+ goto failed;
+ }
+ set_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags);
+ } else
+ BTMTK_INFO("%s, intr_iso_rx_ep missing, don't submit_intr_ble_isoc_urb!",
+ __func__);
+ } else if (BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ /*need to do in bt_open in btmtk_main.c */
+ /* btmtk_usb_send_power_on_cmd_7668(hdev); */
+ }
+ }
+ err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
+ if (err < 0)
+ goto failed;
+
+ err = btusb_submit_bulk_urb(hdev, GFP_KERNEL);
+ if (err < 0) {
+ usb_kill_anchored_urbs(&cif_dev->intr_anchor);
+ goto failed;
+ }
+
+ set_bit(BTUSB_BULK_RUNNING, &bdev->flags);
+
+done:
+ if (state != BTMTK_STATE_SUSPEND
+ && state != BTMTK_STATE_RESUME
+ && state != BTMTK_STATE_STANDBY)
+ usb_autopm_put_interface(cif_dev->intf);
+ return 0;
+
+failed:
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ if (state != BTMTK_STATE_SUSPEND
+ && state != BTMTK_STATE_RESUME
+ && state != BTMTK_STATE_STANDBY)
+ usb_autopm_put_interface(cif_dev->intf);
+ return err;
+}
+
+static void btusb_stop_traffic(struct btmtk_usb_dev *cif_dev)
+{
+ usb_kill_anchored_urbs(&cif_dev->intr_anchor);
+ usb_kill_anchored_urbs(&cif_dev->bulk_anchor);
+ usb_kill_anchored_urbs(&cif_dev->isoc_anchor);
+ usb_kill_anchored_urbs(&cif_dev->ctrl_anchor);
+ usb_kill_anchored_urbs(&cif_dev->ble_isoc_anchor);
+}
+
+static int btmtk_usb_close(struct hci_dev *hdev)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int err;
+ unsigned char state = 0;
+
+ BTMTK_INFO("%s enter!", __func__);
+
+ BTMTK_DBG("%s", hdev->name);
+
+ cancel_work_sync(&bdev->work);
+ cancel_work_sync(&bdev->waker);
+
+ clear_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_BULK_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_WMT_RUNNING, &bdev->flags);
+
+ btusb_stop_traffic(cif_dev);
+ btusb_free_frags(bdev);
+
+ state = btmtk_get_chip_state(bdev);
+ if (state != BTMTK_STATE_SUSPEND
+ && state != BTMTK_STATE_RESUME
+ && state != BTMTK_STATE_STANDBY) {
+ err = usb_autopm_get_interface(cif_dev->intf);
+ if (err < 0)
+ goto failed;
+ }
+
+ cif_dev->intf->needs_remote_wakeup = 0;
+
+ if (state != BTMTK_STATE_SUSPEND
+ && state != BTMTK_STATE_RESUME
+ && state != BTMTK_STATE_STANDBY)
+ usb_autopm_put_interface(cif_dev->intf);
+
+failed:
+ return 0;
+}
+
+/* Maybe will be used in the future*/
+#if 0
+static int btusb_flush(struct hci_dev *hdev)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+
+ BTMTK_DBG("%s", hdev->name);
+
+ usb_kill_anchored_urbs(&bdev->tx_anchor);
+ btusb_free_frags(bdev);
+
+ return 0;
+}
+#endif
+
+static struct urb *alloc_intr_iso_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct urb *urb;
+ unsigned int pipe;
+
+ if (!cif_dev->intr_iso_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndintpipe(cif_dev->udev, cif_dev->intr_iso_tx_ep->bEndpointAddress);
+
+ usb_fill_int_urb(urb, cif_dev->udev, pipe,
+ skb->data, skb->len, btusb_tx_complete, skb, 1);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static struct urb *alloc_ctrl_bgf1_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct usb_ctrlrequest *dr;
+ struct urb *urb;
+ unsigned int pipe;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ BTMTK_DBG("%s\n", __func__);
+ dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+ if (!dr) {
+ usb_free_urb(urb);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dr->bRequestType = 0x21;
+ dr->bRequest = 0x00;
+ dr->wIndex = 3;
+ dr->wValue = 0;
+ dr->wLength = __cpu_to_le16(skb->len);
+
+ pipe = usb_sndctrlpipe(cif_dev->udev, 0x00);
+
+ usb_fill_control_urb(urb, cif_dev->udev, pipe, (void *)dr,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static struct urb *alloc_bulk_cmd_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct urb *urb;
+ unsigned int pipe;
+
+ BTMTK_DBG("%s start\n", __func__);
+ if (!cif_dev->bulk_cmd_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ BTMTK_DBG("%s\n", __func__);
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndbulkpipe(cif_dev->udev, cif_dev->bulk_cmd_tx_ep->bEndpointAddress);
+
+ usb_fill_bulk_urb(urb, cif_dev->udev, pipe,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct usb_ctrlrequest *dr;
+ struct urb *urb;
+ unsigned int pipe;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+ if (!dr) {
+ usb_free_urb(urb);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dr->bRequestType = cif_dev->cmdreq_type;
+ dr->bRequest = cif_dev->cmdreq;
+ dr->wIndex = 0;
+ dr->wValue = 0;
+ dr->wLength = __cpu_to_le16(skb->len);
+
+ pipe = usb_sndctrlpipe(cif_dev->udev, 0x00);
+
+ usb_fill_control_urb(urb, cif_dev->udev, pipe, (void *)dr,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct urb *urb;
+ unsigned int pipe;
+
+ if (!cif_dev->bulk_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndbulkpipe(cif_dev->udev, cif_dev->bulk_tx_ep->bEndpointAddress);
+
+ usb_fill_bulk_urb(urb, cif_dev->udev, pipe,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static struct urb *alloc_isoc_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct urb *urb;
+ unsigned int pipe;
+
+ if (!cif_dev->isoc_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndisocpipe(cif_dev->udev, cif_dev->isoc_tx_ep->bEndpointAddress);
+
+ usb_fill_int_urb(urb, cif_dev->udev, pipe,
+ skb->data, skb->len, btusb_isoc_tx_complete,
+ skb, cif_dev->isoc_tx_ep->bInterval);
+
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ __fill_isoc_descriptor(urb, skb->len,
+ le16_to_cpu(cif_dev->isoc_tx_ep->wMaxPacketSize));
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
+static int submit_tx_urb(struct hci_dev *hdev, struct urb *urb, int type)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int err;
+
+ switch (type) {
+ case HCI_COMMAND_PKT:
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_CMD_HIF,
+ urb->transfer_buffer, urb->transfer_buffer_length);
+ break;
+ case HCI_ACLDATA_PKT:
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_TX_ACL_HIF,
+ urb->transfer_buffer, urb->transfer_buffer_length);
+ break;
+ case HCI_ISO_PKT:
+ btmtk_hci_snoop_save(HCI_SNOOP_TYPE_TX_ISO_HIF,
+ urb->transfer_buffer, urb->transfer_buffer_length);
+ break;
+ default:
+ BTMTK_INFO("%s: invalid type(%d)", __func__, type);
+ }
+
+ usb_anchor_urb(urb, &cif_dev->tx_anchor);
+
+ err = usb_submit_urb(urb, GFP_KERNEL);
+ if (err < 0) {
+ if (err != -EPERM && err != -ENODEV)
+ BTMTK_ERR("%s urb %p submission failed (%d)",
+ hdev->name, urb, -err);
+ kfree(urb->setup_packet);
+ usb_unanchor_urb(urb);
+ } else {
+ usb_mark_last_busy(cif_dev->udev);
+ }
+
+ usb_free_urb(urb);
+ return err;
+}
+
+static int submit_or_queue_tx_urb(struct hci_dev *hdev, struct urb *urb, int type)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ unsigned long flags;
+ bool suspending;
+
+ spin_lock_irqsave(&bdev->txlock, flags);
+ suspending = test_bit(BTUSB_SUSPENDING, &bdev->flags);
+ if (!suspending)
+ bdev->tx_in_flight++;
+ spin_unlock_irqrestore(&bdev->txlock, flags);
+
+ if (!suspending)
+ return submit_tx_urb(hdev, urb, type);
+
+ schedule_work(&bdev->waker);
+
+ usb_free_urb(urb);
+ return 0;
+}
+
+static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct urb *urb = NULL;
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ unsigned int ifnum_base;
+ int ret = 0;
+ struct sk_buff *iso_skb = NULL;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+ struct sk_buff *evt_skb;
+ uint8_t notify_alt_evt[NOTIFY_ALT_EVT_LEN] = {0x04, 0x0E, 0x04, 0x01, 0x03, 0x0c, 0x00};
+ u16 crBaseAddr = 0, crRegOffset = 0;
+ unsigned char *skb_tmp = NULL;
+
+ if (skb->len <= 0) {
+ ret = -EFAULT;
+ BTMTK_ERR("%s: target packet length:%zu is not allowed", __func__, (size_t)skb->len);
+ }
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+
+ skb_pull(skb, 1);
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send_frame, type = %d", __func__,
+ hci_skb_pkt_type(skb));
+ switch (hci_skb_pkt_type(skb)) {
+ case HCI_COMMAND_PKT:
+ if (skb->len > 7) {
+ if (skb->data[0] == 0x6f && skb->data[1] == 0xfc &&
+ skb->data[2] == 0x06 && skb->data[3] == 0x01 &&
+ skb->data[4] == 0xff && skb->data[5] == 0x03 &&
+ skb->data[6] == 0x00 && skb->data[7] == 0x00) {
+ /* return evt to upper layered */
+ evt_skb = skb_copy(skb, GFP_KERNEL);
+ if (!evt_skb) {
+ BTMTK_ERR("%s skb_copy failed", __func__);
+ return -ENOMEM;
+ }
+
+ bt_cb(evt_skb)->pkt_type = notify_alt_evt[0];
+ memcpy(evt_skb->data, ¬ify_alt_evt[1], NOTIFY_ALT_EVT_LEN - 1);
+ evt_skb->len = NOTIFY_ALT_EVT_LEN - 1;
+ /* After set alternate setting, we will return evt to boots */
+ hci_recv_frame(hdev, evt_skb);
+ hdev->conn_hash.sco_num++;
+ bdev->sco_num = hdev->conn_hash.sco_num;
+ cif_dev->new_isoc_altsetting = skb->data[8];
+ BTMTK_INFO("alt_setting = %d, new_isoc_altsetting_interface = %d\n",
+ cif_dev->new_isoc_altsetting, cif_dev->new_isoc_altsetting_interface);
+ schedule_work(&bdev->work);
+ msleep(20);
+ kfree_skb(skb);
+ skb = NULL;
+ return 0;
+ } else if (skb->data[0] == 0x6f && skb->data[1] == 0xfc &&
+ skb->data[2] == 0x07 && skb->data[3] == 0x01 &&
+ skb->data[4] == 0xff && skb->data[5] == 0x03 &&
+ skb->data[6] == 0x00 && skb->data[7] == 0x00 && skb->data[9] == 0x00) {
+ evt_skb = skb_copy(skb, GFP_KERNEL);
+ if (!evt_skb) {
+ BTMTK_ERR("%s skb_copy failed", __func__);
+ return -ENOMEM;
+ }
+
+ bt_cb(evt_skb)->pkt_type = notify_alt_evt[0];
+ memcpy(evt_skb->data, ¬ify_alt_evt[1], NOTIFY_ALT_EVT_LEN - 1);
+ evt_skb->len = NOTIFY_ALT_EVT_LEN - 1;
+ /* After set alternate setting, we will return evt to boots */
+ hci_recv_frame(hdev, evt_skb);
+ /* if sco_num == 0, btusb_work will set alternate setting to zero */
+ hdev->conn_hash.sco_num--;
+ bdev->sco_num = hdev->conn_hash.sco_num;
+ cif_dev->new_isoc_altsetting_interface = skb->data[8];
+ BTMTK_INFO("alt_setting to = %d, new_isoc_altsetting_interface = %d\n",
+ cif_dev->new_isoc_altsetting, cif_dev->new_isoc_altsetting_interface);
+ schedule_work(&bdev->work);
+ /* If we don't sleep 50ms, it will failed to set alternate setting to zero */
+ msleep(50);
+ kfree_skb(skb);
+ skb = NULL;
+ return 0;
+ } else if (skb->data[0] == 0x6f && skb->data[1] == 0xfc &&
+ skb->data[2] == 0x09 && skb->data[3] == 0x01 &&
+ skb->data[4] == 0xff && skb->data[5] == 0x05 &&
+ skb->data[6] == 0x00 && skb->data[7] == 0x01) {
+ BTMTK_INFO("read CR skb->data = %02x %02x %02x %02x\n", skb->data[8],
+ skb->data[9], skb->data[10], skb->data[11]);
+ crBaseAddr = (skb->data[8]<<8) + skb->data[9];
+ crRegOffset = (skb->data[10]<<8) + skb->data[11];
+ BTMTK_INFO("base + offset = %04x %04x\n", crBaseAddr, crRegOffset);
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+ ret = usb_control_msg(cif_dev->udev, usb_rcvctrlpipe(cif_dev->udev, 0),
+ 1, 0xDE, crBaseAddr, crRegOffset,
+ bdev->io_buf, 4, USB_CTRL_IO_TIMO);
+ if (ret < 0)
+ BTMTK_ERR("read CR(%04X[%04X]) FAILED\n", crBaseAddr, crRegOffset);
+ else
+ BTMTK_INFO("read CR(%04X[%04X]) value = 0x%02x%02x%02x%02x\n",
+ crBaseAddr, crRegOffset,
+ bdev->io_buf[3], bdev->io_buf[2],
+ bdev->io_buf[1], bdev->io_buf[0]);
+ kfree_skb(skb);
+ skb = NULL;
+ return 0;
+ } else if (skb->data[0] == 0x6f && skb->data[1] == 0xfc &&
+ skb->data[2] == 0x0D && skb->data[3] == 0x01 &&
+ skb->data[4] == 0xff && skb->data[5] == 0x09 &&
+ skb->data[6] == 0x00 && skb->data[7] == 0x02) {
+ crBaseAddr = (skb->data[8] << 8) + skb->data[9];
+ crRegOffset = (skb->data[10] << 8) + skb->data[11];
+ BTMTK_INFO("base + offset = %04x %04x\n", crBaseAddr, crRegOffset);
+ memset(cif_dev->o_usb_buf, 0, HCI_MAX_COMMAND_SIZE);
+ cif_dev->o_usb_buf[0] = skb->data[12];
+ cif_dev->o_usb_buf[1] = skb->data[13];
+ cif_dev->o_usb_buf[2] = skb->data[14];
+ cif_dev->o_usb_buf[3] = skb->data[15];
+ ret = usb_control_msg(cif_dev->udev, usb_sndctrlpipe(cif_dev->udev, 0),
+ 2, 0x5E, crBaseAddr, crRegOffset,
+ cif_dev->o_usb_buf, 4, USB_CTRL_IO_TIMO);
+ if (ret < 0)
+ BTMTK_ERR("write CR(%04X[%04X]) FAILED\n", crBaseAddr, crRegOffset);
+ else
+ BTMTK_INFO("write CR(%04X[%04X]) value = 0x%02x%02x%02x%02x\n",
+ crBaseAddr, crRegOffset,
+ cif_dev->o_usb_buf[3], cif_dev->o_usb_buf[2],
+ cif_dev->o_usb_buf[1], cif_dev->o_usb_buf[0]);
+ kfree_skb(skb);
+ skb = NULL;
+ return 0;
+ }
+ }
+
+ /* For wmt cmd/evt */
+ if (!memcmp(skb->data, &bmain_info->wmt_over_hci_header[1], WMT_OVER_HCI_HEADER_SIZE - 1)) {
+ skb_push(skb, 1);
+ skb->data[0] = MTK_HCI_COMMAND_PKT;
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, 6ffc send_frame", __func__);
+ /* No event for wmt trigger assert command */
+ if (memcmp(skb->data, wmt_trigger_assert, WMT_TRIGGER_ASSERT_LEN)) {
+ ret = btmtk_usb_send_cmd(bdev, skb, WMT_DELAY_TIMES, RETRY_TIMES,
+ BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: send wmt cmd failed", __func__);
+ return ret;
+ }
+ set_bit(BTUSB_WMT_RUNNING, &bdev->flags);
+ ret = btusb_submit_wmt_urb(hdev, GFP_KERNEL);
+ } else {
+ BTMTK_INFO("%s: Trigger FW assert by WMT command", __func__);
+ ret = btmtk_usb_send_cmd(bdev, skb, WMT_DELAY_TIMES, RETRY_TIMES,
+ BTMTK_TX_CMD_FROM_DRV);
+ }
+ return ret;
+ }
+
+ if (BTMTK_IS_BT_0_INTF(ifnum_base)) {
+ if ((is_mt6639(bdev->chip_id) || is_mt7902(bdev->chip_id)
+ || is_mt7922(bdev->chip_id) || is_mt7961(bdev->chip_id)) &&
+ cif_dev->bulk_cmd_tx_ep)
+ urb = alloc_bulk_cmd_urb(hdev, skb);
+ else
+ urb = alloc_ctrl_urb(hdev, skb);
+ } else if (BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ if (is_mt7961(bdev->chip_id)) {
+ if (cif_dev->bulk_cmd_tx_ep) {
+ UNUSED(alloc_ctrl_bgf1_urb);
+ urb = alloc_bulk_cmd_urb(hdev, skb);
+ } else
+ urb = alloc_ctrl_bgf1_urb(hdev, skb);
+ } else {
+ BTMTK_ERR("%s: chip_id(%d) is invalid", __func__, bdev->chip_id);
+ return -ENODEV;
+ }
+ } else {
+ BTMTK_ERR("%s: ifnum_base(%d) is invalid", __func__, ifnum_base);
+ return -ENODEV;
+ }
+
+ if (IS_ERR(urb))
+ return PTR_ERR(urb);
+
+ hdev->stat.cmd_tx++;
+ return submit_or_queue_tx_urb(hdev, urb, HCI_COMMAND_PKT);
+
+ case HCI_ACLDATA_PKT:
+ if (skb->data[0] == 0x00 && skb->data[1] == 0x44) {
+ if (cif_dev->iso_channel && bdev->iso_threshold) {
+ int isoc_pkt_len = 0;
+ int isoc_pkt_padding = 0;
+
+ skb_pull(skb, 4);
+ isoc_pkt_len = skb->data[2] + (skb->data[3] << 8) + HCI_ISO_PKT_HEADER_SIZE;
+ isoc_pkt_padding = bdev->iso_threshold - isoc_pkt_len;
+
+ if (skb_tailroom(skb) >= isoc_pkt_padding) {
+ skb_tmp = skb_put(skb, isoc_pkt_padding);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed!", __func__);
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ memset(skb_tmp, 0, isoc_pkt_padding);
+ urb = alloc_intr_iso_urb(hdev, skb);
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, it's ble iso packet",
+ __func__);
+ if (IS_ERR(urb))
+ return PTR_ERR(urb);
+ } else {
+ /* hci driver alllocate the size of skb that is to small, need re-allocate */
+ iso_skb = alloc_skb(HCI_MAX_ISO_SIZE + BT_SKB_RESERVE, GFP_ATOMIC);
+ if (!iso_skb) {
+ BTMTK_ERR("%s allocate skb failed!!", __func__);
+ return -ENOMEM;
+ }
+ /* copy skb data into iso_skb */
+ skb_copy_bits(skb, 0, skb_put(iso_skb, skb->len), skb->len);
+ skb_tmp = skb_put(iso_skb, isoc_pkt_padding);
+ if (!skb_tmp) {
+ BTMTK_ERR("%s, skb_put failed!", __func__);
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ memset(skb_tmp, 0, isoc_pkt_padding);
+
+ /* After call back, bt drive will free iso_skb */
+ urb = alloc_intr_iso_urb(hdev, iso_skb);
+ BTMTK_DBG_RAW(iso_skb->data, iso_skb->len, "%s, it's ble iso packet",
+ __func__);
+
+ if (IS_ERR(urb)) {
+ kfree_skb(iso_skb);
+ iso_skb = NULL;
+ return PTR_ERR(urb);
+ }
+
+ hdev->stat.acl_tx++;
+ ret = submit_or_queue_tx_urb(hdev, urb, HCI_ISO_PKT);
+ if (ret < 0) {
+ /* when ret < 0, skb will be free in hci_send_frame,
+ * but need to free iso_skb, because iso_skb alloc in bt driver
+ */
+ kfree_skb(iso_skb);
+ iso_skb = NULL;
+ goto exit;
+ }
+
+ /* It's alloc by hci drver, bt driver must free it when ret >=0. */
+ kfree_skb(skb);
+ skb = NULL;
+exit:
+ return ret;
+ }
+ } else {
+ BTMTK_WARN("btusb_send_frame send iso data, but iso channel not exit, %d",
+ bdev->iso_threshold);
+ /* if iso channel not exist, we need to drop iso data then free the skb */
+ kfree_skb(skb);
+ skb = NULL;
+ return 0;
+ }
+ } else {
+ urb = alloc_bulk_urb(hdev, skb);
+ if (IS_ERR(urb))
+ return PTR_ERR(urb);
+ }
+ hdev->stat.acl_tx++;
+ return submit_or_queue_tx_urb(hdev, urb, HCI_ACLDATA_PKT);
+
+ case HCI_SCODATA_PKT:
+ if (hci_conn_num(hdev, SCO_LINK) < 1) {
+ BTMTK_INFO("btusb_send_frame hci_conn sco link = %d", hci_conn_num(hdev, SCO_LINK));
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ return -ENODEV;
+#else
+ /* We need to study how to solve this in dvt case.*/
+#endif
+ }
+
+ urb = alloc_isoc_urb(hdev, skb);
+ if (IS_ERR(urb))
+ return PTR_ERR(urb);
+
+ hdev->stat.sco_tx++;
+ return submit_tx_urb(hdev, urb, HCI_SCODATA_PKT);
+ }
+
+ return -EILSEQ;
+}
+
+static int btmtk_usb_load_fw_patch_using_dma(struct btmtk_dev *bdev, u8 *image,
+ u8 *fwbuf, int section_dl_size, int section_offset)
+{
+ int cur_len = 0;
+ int ret = 0;
+ u32 value = 0;
+ s32 sent_len;
+ int retry = WMT_DELAY_TIMES;
+ int delay = 0;
+ u32 dma_done_cr_r, dma_done_cr_w;
+ u32 reg_value = 0;
+ u32 dma_done_value = 0;
+ u8 dl_done_cmd[LD_PATCH_CMD_LEN] = {0x01, 0x6F, 0xFC, 0x05, 0x01, 0x01, 0x01, 0x00, PATCH_PHASE3};
+ u8 event[LD_PATCH_EVT_LEN] = {0x04, 0xE4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; /* event[7] is status*/
+
+ if (bdev == NULL || image == NULL || fwbuf == NULL) {
+ BTMTK_ERR("%s: invalid parameters!", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ BTMTK_INFO("%s: loading rom patch... start", __func__);
+ while (1) {
+ sent_len = (section_dl_size - cur_len) >= (UPLOAD_PATCH_UNIT - HCI_TYPE_SIZE) ?
+ (UPLOAD_PATCH_UNIT - HCI_TYPE_SIZE) : (section_dl_size - cur_len);
+
+ if (sent_len > 0) {
+ /* btmtk_cif_send_bulk_out will send from image[1],
+ * image[0] will be ingored
+ */
+ image[0] = HCI_ACLDATA_PKT;
+ memcpy(&image[HCI_TYPE_SIZE], fwbuf + section_offset + cur_len, sent_len);
+ BTMTK_DBG("%s: sent_len = %d, cur_len = %d", __func__,
+ sent_len, cur_len);
+ ret = btmtk_main_send_cmd(bdev,
+ image, sent_len + HCI_TYPE_SIZE,
+ NULL, -1,
+ 0, 0, BTMTK_TX_ACL_FROM_DRV);
+ if (ret < 0) {
+ BTMTK_ERR("%s: send patch failed, terminate", __func__);
+ goto exit;
+ }
+ cur_len += sent_len;
+ } else
+ break;
+ }
+
+ if (is_mt6639(bdev->chip_id)) {
+ delay = PATCH_DOWNLOAD_PHASE3_SECURE_BOOT_DELAY_TIME;
+ reg_value = BT_GDMA_DONE_6639_VALUE_W;
+ } else if (is_mt7922(bdev->chip_id) || is_mt7902(bdev->chip_id)) {
+ delay = PATCH_DOWNLOAD_PHASE3_SECURE_BOOT_DELAY_TIME;
+ reg_value = BT_GDMA_DONE_7922_VALUE_W;
+ } else {
+ delay = DELAY_TIMES;
+ reg_value = BT_GDMA_DONE_7921_VALUE_W;
+ }
+
+ if (is_mt6639(bdev->chip_id)) {
+ dma_done_cr_w = BT_GDMA_DONE_6639_ADDR_W;
+ dma_done_cr_r = BT_GDMA_DONE_6639_ADDR_R;
+ dma_done_value = BT_GDMA_DONE_6639_VALUE_R;
+ } else {
+ dma_done_cr_w = BT_GDMA_DONE_ADDR_W;
+ dma_done_cr_r = BT_GDMA_DONE_ADDR_R;
+ dma_done_value = BT_GDMA_DONE_VALUE_R;
+ }
+
+ /* Poll the register until dma dl is completed */
+ if (is_mt6639(bdev->chip_id) || is_mt7961(bdev->chip_id) || is_mt7922(bdev->chip_id)) {
+ do {
+ btmtk_cif_write_uhw_register(bdev, dma_done_cr_w, reg_value);
+ btmtk_cif_read_uhw_register(bdev, dma_done_cr_r, &value);
+ if ((value & dma_done_value) == value)
+ break;
+ msleep(DELAY_TIMES);
+ } while (retry-- > 0);
+
+ if ((value & dma_done_value) != value) {
+ BTMTK_INFO("%s: DL Failed cr=%08X", __func__, value);
+ ret = -1;
+ btmtk_send_assert_cmd(bdev);
+ goto exit;
+ }
+ }
+
+ BTMTK_INFO_RAW(dl_done_cmd, LD_PATCH_CMD_LEN, "%s: send dl cmd - ", __func__);
+ ret = btmtk_main_send_cmd(bdev, dl_done_cmd, LD_PATCH_CMD_LEN,
+ event, LD_PATCH_EVT_LEN,
+ delay, RETRY_TIMES, BTMTK_TX_CMD_FROM_DRV);
+ if (ret < 0)
+ BTMTK_ERR("%s: send wmd dl cmd failed, terminate!", __func__);
+ BTMTK_INFO("%s: loading rom patch... Done", __func__);
+
+exit:
+ return ret;
+}
+
+static void btusb_notify(struct hci_dev *hdev, unsigned int evt)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+
+ BTMTK_DBG("%s evt %d", hdev->name, evt);
+
+ if (hci_conn_num(hdev, SCO_LINK) != bdev->sco_num) {
+ bdev->sco_num = hci_conn_num(hdev, SCO_LINK);
+ schedule_work(&bdev->work);
+ }
+}
+
+static inline int __set_isoc_interface(struct hci_dev *hdev, int altsetting)
+{
+ struct btmtk_dev *bdev = hci_get_drvdata(hdev);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct usb_interface *intf = cif_dev->isoc;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i, err;
+ unsigned int ifnum_base;
+
+ if (!cif_dev->isoc)
+ return -ENODEV;
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ if (BTMTK_IS_BT_0_INTF(ifnum_base))
+ cif_dev->new_isoc_altsetting_interface = 1;
+ else if (BTMTK_IS_BT_1_INTF(ifnum_base))
+ cif_dev->new_isoc_altsetting_interface = 4;
+ err = usb_set_interface(cif_dev->udev, cif_dev->new_isoc_altsetting_interface, altsetting);
+ BTMTK_DBG("setting interface alt = %d, interface = %d", altsetting, cif_dev->new_isoc_altsetting_interface);
+
+ if (err < 0) {
+ BTMTK_ERR("%s setting interface failed (%d)", hdev->name, -err);
+ return err;
+ }
+
+ bdev->isoc_altsetting = altsetting;
+
+ cif_dev->isoc_tx_ep = NULL;
+ cif_dev->isoc_rx_ep = NULL;
+
+ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
+ ep_desc = &intf->cur_altsetting->endpoint[i].desc;
+
+ if (!cif_dev->isoc_tx_ep && usb_endpoint_is_isoc_out(ep_desc)) {
+ cif_dev->isoc_tx_ep = ep_desc;
+ continue;
+ }
+
+ if (!cif_dev->isoc_rx_ep && usb_endpoint_is_isoc_in(ep_desc)) {
+ cif_dev->isoc_rx_ep = ep_desc;
+ continue;
+ }
+ }
+
+ if (!cif_dev->isoc_tx_ep || !cif_dev->isoc_rx_ep) {
+ BTMTK_ERR("%s invalid SCO descriptors", hdev->name);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void btusb_work(struct work_struct *work)
+{
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, work);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct hci_dev *hdev = bdev->hdev;
+ int new_alts;
+ int err;
+ unsigned long flags;
+
+ if (bdev->sco_num > 0) {
+ if (!test_bit(BTUSB_DID_ISO_RESUME, &bdev->flags)) {
+ err = usb_autopm_get_interface(cif_dev->isoc ? cif_dev->isoc : cif_dev->intf);
+ if (err < 0) {
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ usb_kill_anchored_urbs(&cif_dev->isoc_anchor);
+ return;
+ }
+
+ set_bit(BTUSB_DID_ISO_RESUME, &bdev->flags);
+ }
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ new_alts = cif_dev->new_isoc_altsetting;
+#else
+ if (hdev->voice_setting & 0x0020) {
+ static const int alts[3] = { 2, 4, 5 };
+
+ new_alts = alts[bdev->sco_num - 1];
+ } else {
+ new_alts = bdev->sco_num;
+ }
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ usb_kill_anchored_urbs(&cif_dev->isoc_anchor);
+
+ /* When isochronous alternate setting needs to be
+ * changed, because SCO connection has been added
+ * or removed, a packet fragment may be left in the
+ * reassembling state. This could lead to wrongly
+ * assembled fragments.
+ *
+ * Clear outstanding fragment when selecting a new
+ * alternate setting.
+ */
+ spin_lock_irqsave(&bdev->rxlock, flags);
+ kfree_skb(bdev->sco_skb);
+ bdev->sco_skb = NULL;
+ spin_unlock_irqrestore(&bdev->rxlock, flags);
+
+ if (__set_isoc_interface(hdev, new_alts) < 0)
+ return;
+
+ if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &bdev->flags)) {
+ if (btusb_submit_isoc_urb(hdev, GFP_KERNEL) < 0)
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ else
+ btusb_submit_isoc_urb(hdev, GFP_KERNEL);
+ }
+ } else {
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ usb_kill_anchored_urbs(&cif_dev->isoc_anchor);
+ BTMTK_INFO("%s set alt to zero", __func__);
+ __set_isoc_interface(hdev, 0);
+ if (test_and_clear_bit(BTUSB_DID_ISO_RESUME, &bdev->flags))
+ usb_autopm_put_interface(cif_dev->isoc ? cif_dev->isoc : cif_dev->intf);
+ }
+}
+
+static void btusb_waker(struct work_struct *work)
+{
+ struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, waker);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int err;
+
+ err = usb_autopm_get_interface(cif_dev->intf);
+ if (err < 0)
+ return;
+
+ usb_autopm_put_interface(cif_dev->intf);
+}
+
+int btmtk_usb_toggle_rst_pin(struct btmtk_dev *bdev)
+{
+ struct device_node *node;
+ int rst_pin_num = 0;
+ int cur = 0;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+
+ cur = atomic_cmpxchg(&bmain_info->chip_reset, BTMTK_RESET_DONE, BTMTK_RESET_DOING);
+ if (cur == BTMTK_RESET_DOING) {
+ BTMTK_INFO("%s: reset in progress, return", __func__);
+ return -1;
+ }
+
+ if (!bdev) {
+ BTMTK_WARN("%s: bdev is NULL!", __func__);
+ return -1;
+ }
+ if (bdev->bt_cfg.dongle_reset_gpio_pin == -1) {
+ BTMTK_WARN("%s: bt driver is not ready, please don't call chip reset!", __func__);
+ return -1;
+ }
+
+ BTMTK_INFO("%s: begin", __func__);
+
+ /* Initialize the interface specific function pointers */
+ reset_func.pf_pdwndFunc = (pdwnc_func) btmtk_kallsyms_lookup_name("PDWNC_SetBTInResetState");
+ if (reset_func.pf_pdwndFunc)
+ BTMTK_INFO("%s: Found PDWNC_SetBTInResetState", __func__);
+ else
+ BTMTK_WARN("%s: No Exported Func Found PDWNC_SetBTInResetState", __func__);
+
+ reset_func.pf_resetFunc2 = (reset_func_ptr2) btmtk_kallsyms_lookup_name("mtk_gpio_set_value");
+ if (!reset_func.pf_resetFunc2)
+ BTMTK_ERR("%s: No Exported Func Found mtk_gpio_set_value", __func__);
+ else
+ BTMTK_INFO("%s: Found mtk_gpio_set_value", __func__);
+
+ reset_func.pf_lowFunc = (set_gpio_low) btmtk_kallsyms_lookup_name("MDrv_GPIO_Set_Low");
+ reset_func.pf_highFunc = (set_gpio_high) btmtk_kallsyms_lookup_name("MDrv_GPIO_Set_High");
+ if (!reset_func.pf_lowFunc || !reset_func.pf_highFunc)
+ BTMTK_WARN("%s: No Exported Func Found MDrv_GPIO_Set_Low or High", __func__);
+ else
+ BTMTK_INFO("%s: Found MDrv_GPIO_Set_Low & MDrv_GPIO_Set_High", __func__);
+
+ if (reset_func.pf_pdwndFunc) {
+ BTMTK_INFO("%s: Invoke PDWNC_SetBTInResetState(%d)", __func__, 1);
+ reset_func.pf_pdwndFunc(1);
+ } else
+ BTMTK_INFO("%s: No Exported Func Found PDWNC_SetBTInResetState", __func__);
+
+ if (reset_func.pf_resetFunc2) {
+ rst_pin_num = bdev->bt_cfg.dongle_reset_gpio_pin;
+ BTMTK_INFO("%s: Invoke bdev->pf_resetFunc2(%d,%d)", __func__, rst_pin_num, 0);
+ reset_func.pf_resetFunc2(rst_pin_num, 0);
+ msleep(RESET_PIN_SET_LOW_TIME);
+ BTMTK_INFO("%s: Invoke bdev->pf_resetFunc2(%d,%d)", __func__, rst_pin_num, 1);
+ reset_func.pf_resetFunc2(rst_pin_num, 1);
+ goto exit;
+ }
+
+ node = of_find_compatible_node(NULL, NULL, "mstar,gpio-wifi-ctl");
+ if (node) {
+ if (of_property_read_u32(node, "wifi-ctl-gpio", &rst_pin_num) == 0) {
+ if (reset_func.pf_lowFunc && reset_func.pf_highFunc) {
+ BTMTK_INFO("%s: Invoke bdev->pf_lowFunc(%d)", __func__, rst_pin_num);
+ reset_func.pf_lowFunc(rst_pin_num);
+ msleep(RESET_PIN_SET_LOW_TIME);
+ BTMTK_INFO("%s: Invoke bdev->pf_highFunc(%d)", __func__, rst_pin_num);
+ reset_func.pf_highFunc(rst_pin_num);
+ goto exit;
+ }
+ } else
+ BTMTK_WARN("%s, failed to obtain wifi control gpio\n", __func__);
+ } else {
+ if (reset_func.pf_lowFunc && reset_func.pf_highFunc) {
+ rst_pin_num = bdev->bt_cfg.dongle_reset_gpio_pin;
+ BTMTK_INFO("%s: Invoke bdev->pf_lowFunc(%d)", __func__, rst_pin_num);
+ reset_func.pf_lowFunc(rst_pin_num);
+ msleep(RESET_PIN_SET_LOW_TIME);
+ BTMTK_INFO("%s: Invoke bdev->pf_highFunc(%d)", __func__, rst_pin_num);
+ reset_func.pf_highFunc(rst_pin_num);
+ goto exit;
+ }
+ }
+
+ /* use linux kernel common api */
+ do {
+ struct device_node *node;
+ int mt76xx_reset_gpio = bdev->bt_cfg.dongle_reset_gpio_pin;
+
+ node = of_find_compatible_node(NULL, NULL, "mediatek,connectivity-combo");
+ if (node) {
+ mt76xx_reset_gpio = of_get_named_gpio(node, "mt76xx-reset-gpio", 0);
+ if (gpio_is_valid(mt76xx_reset_gpio))
+ BTMTK_INFO("%s: Get chip reset gpio(%d)", __func__, mt76xx_reset_gpio);
+ else
+ mt76xx_reset_gpio = bdev->bt_cfg.dongle_reset_gpio_pin;
+ }
+
+ BTMTK_INFO("%s: Invoke Low(%d)", __func__, mt76xx_reset_gpio);
+ gpio_direction_output(mt76xx_reset_gpio, 0);
+ msleep(RESET_PIN_SET_LOW_TIME);
+ BTMTK_INFO("%s: Invoke High(%d)", __func__, mt76xx_reset_gpio);
+ gpio_direction_output(mt76xx_reset_gpio, 1);
+ goto exit;
+ } while (0);
+
+exit:
+ BTMTK_INFO("%s: end", __func__);
+ return 0;
+}
+EXPORT_SYMBOL(btmtk_usb_toggle_rst_pin);
+
+static int btmtk_usb_subsys_reset(struct btmtk_dev *bdev)
+{
+ int val, retry = 10;
+ u32 mcu_init_done = MCU_BT0_INIT_DONE;
+ u32 bt_subsys_cr;
+
+ cancel_work_sync(&bdev->work);
+ cancel_work_sync(&bdev->waker);
+
+ clear_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_BULK_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_WMT_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_SUSPENDING, &bdev->flags);
+ bdev->sco_num = 0;
+
+ btusb_stop_traffic((struct btmtk_usb_dev *)bdev->cif_dev);
+
+ /* For reset */
+ btmtk_cif_write_uhw_register(bdev, EP_RST_OPT, EP_RST_IN_OUT_OPT);
+
+ /* read interrupt EP15 CR */
+ btmtk_cif_read_uhw_register(bdev, BT_WDT_STATUS, &val);
+
+ if (is_mt6639(bdev->chip_id))
+ bt_subsys_cr = BT_SUBSYS_RST_6639;
+ else
+ bt_subsys_cr = BT_SUBSYS_RST;
+ /* Write Reset CR to 1 */
+ btmtk_cif_read_uhw_register(bdev, bt_subsys_cr, &val);
+ BTMTK_INFO("%s: read Reset CR : 0x%08x", __func__, val);
+ val |= (1 << 0);
+ BTMTK_INFO("%s: write 1 to Reset CR : 0x%08x", __func__, val);
+ btmtk_cif_write_uhw_register(bdev, bt_subsys_cr, val);
+
+ btmtk_cif_write_uhw_register(bdev, UDMA_INT_STA_BT, 0x000000FF);
+ btmtk_cif_read_uhw_register(bdev, UDMA_INT_STA_BT, &val);
+ btmtk_cif_write_uhw_register(bdev, UDMA_INT_STA_BT1, 0x000000FF);
+ btmtk_cif_read_uhw_register(bdev, UDMA_INT_STA_BT1, &val);
+
+ /* Write Reset CR to 0 */
+ btmtk_cif_read_uhw_register(bdev, bt_subsys_cr, &val);
+ BTMTK_INFO("%s: read Reset CR : 0x%08x", __func__, val);
+ val &= ~(1 << 0);
+ BTMTK_INFO("%s: write 0 to Reset CR : 0x%08x", __func__, val);
+ btmtk_cif_write_uhw_register(bdev, bt_subsys_cr, val);
+
+ /* Read reset CR */
+ btmtk_cif_read_uhw_register(bdev, bt_subsys_cr, &val);
+
+ if (bdev->dualBT)
+ mcu_init_done |= MCU_BT1_INIT_DONE;
+
+ do {
+ /* polling re-init CR */
+ btmtk_cif_read_uhw_register(bdev, BT_MISC, &val);
+ BTMTK_INFO("%s: reg=%x, value=0x%08x", __func__, BT_MISC, val);
+ if (val == 0xffffffff) {
+ /* read init CR failed */
+ BTMTK_INFO("%s: read init CR failed, retry = %d", __func__, retry);
+ } else if ((val & mcu_init_done) == mcu_init_done) {
+ /* L0.5 reset done */
+ BTMTK_INFO("%s: Do L0.5 reset sucessfully.", __func__);
+ goto Finish;
+ } else {
+ BTMTK_INFO("%s: polling MCU-init done CR", __func__);
+ }
+ msleep(100);
+ } while (retry-- > 0);
+
+ /* L0.5 reset failed, do whole chip reset */
+ return -1;
+
+Finish:
+ return 0;
+}
+
+static int btmtk_usb_enter_standby(void)
+{
+ int ret = 0;
+ int i = 0;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct btmtk_woble *bt_woble = NULL;
+ int intf_num = btmtk_get_interface_num();
+ struct btmtk_dev **pp_bdev = btmtk_get_pp_bdev();
+
+ BTMTK_INFO("%s: enter", __func__);
+ for (i = 0; i < intf_num; i++) {
+ /* Find valid dev for already probe interface. */
+ if (pp_bdev[i]->hdev != NULL) {
+ bdev = pp_bdev[i];
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ bt_woble = &cif_dev->bt_woble;
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_STANDBY;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s parameter is NULL", __func__);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ btmtk_usb_cif_mutex_lock(bdev);
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btmtk_woble_suspend(bt_woble);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ btmtk_usb_cif_mutex_unlock(bdev);
+
+ if (ret)
+ break;
+ }
+ }
+
+ BTMTK_INFO("%s: end", __func__);
+ return ret;
+}
+
+static int btusb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_endpoint_descriptor *ep_desc;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ unsigned int ifnum_base;
+ int i, err = 0;
+ struct btmtk_main_info *bmain_info = btmtk_get_main_info();
+
+ ifnum_base = intf->cur_altsetting->desc.bInterfaceNumber;
+ BTMTK_INFO("intf %p id %p, interfacenum = %d", intf, id, ifnum_base);
+
+ bdev = usb_get_intfdata(intf);
+ if (!bdev) {
+ BTMTK_ERR("[ERR] bdev is NULL");
+ err = -ENOMEM;
+ goto end;
+ }
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
+ ep_desc = &intf->cur_altsetting->endpoint[i].desc;
+
+ /* reset_intr_ep must be initialized before intr_ep,
+ * otherwise its address may be the intr_ep address
+ */
+ if (!cif_dev->reset_intr_ep && ep_desc->bEndpointAddress == 0x8f &&
+ usb_endpoint_is_int_in(ep_desc)) {
+ BTMTK_INFO("intr_reset_rx__ep i = %d Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress, intf->cur_altsetting->desc.bNumEndpoints);
+ cif_dev->reset_intr_ep = ep_desc;
+ continue;
+ }
+
+ /* bulk_cmd_tx_ep must be initialized before bulk_tx_ep,
+ * otherwise its address will be the bulk_tx_ep address
+ */
+ if (!cif_dev->bulk_cmd_tx_ep && usb_endpoint_is_bulk_out(ep_desc) &&
+ (ep_desc->bEndpointAddress == 0x01 || ep_desc->bEndpointAddress == 0x0b)) {
+ cif_dev->bulk_cmd_tx_ep = ep_desc;
+ BTMTK_INFO(" bulk_cmd_tx_ep i = %d Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress, intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+
+ if (!cif_dev->intr_ep && usb_endpoint_is_int_in(ep_desc)) {
+ cif_dev->intr_ep = ep_desc;
+ BTMTK_INFO("intr_rx_ep i = %d Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress, intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+
+ if (!cif_dev->bulk_tx_ep && usb_endpoint_is_bulk_out(ep_desc)) {
+ cif_dev->bulk_tx_ep = ep_desc;
+ BTMTK_INFO("bulk_tx_ep i = %d Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress, intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+
+ if (!cif_dev->bulk_rx_ep && usb_endpoint_is_bulk_in(ep_desc)) {
+ cif_dev->bulk_rx_ep = ep_desc;
+ BTMTK_INFO("bulk_rx_ep i = %d Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress, intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+ }
+
+ if (!cif_dev->intr_ep || !cif_dev->bulk_tx_ep || !cif_dev->bulk_rx_ep) {
+ BTMTK_ERR("[ERR] intr_ep or bulk_tx_ep or bulk_rx_ep is NULL");
+ err = -ENODEV;
+ goto end;
+ }
+
+ cif_dev->cmdreq_type = USB_TYPE_CLASS;
+ cif_dev->cmdreq = 0x00;
+
+
+ cif_dev->udev = interface_to_usbdev(intf);
+ cif_dev->intf = intf;
+ bdev->intf_dev = &cif_dev->udev->dev;
+
+ INIT_WORK(&bdev->work, btusb_work);
+ INIT_WORK(&bdev->waker, btusb_waker);
+ /* it's for L0/L0.5 reset */
+ INIT_WORK(&bdev->reset_waker, btmtk_reset_waker);
+ init_usb_anchor(&cif_dev->tx_anchor);
+ spin_lock_init(&bdev->txlock);
+
+ init_usb_anchor(&cif_dev->intr_anchor);
+ init_usb_anchor(&cif_dev->bulk_anchor);
+ init_usb_anchor(&cif_dev->isoc_anchor);
+ init_usb_anchor(&cif_dev->ctrl_anchor);
+ init_usb_anchor(&cif_dev->ble_isoc_anchor);
+ spin_lock_init(&bdev->rxlock);
+
+ err = btmtk_cif_allocate_memory(cif_dev);
+ if (err < 0) {
+ BTMTK_ERR("[ERR] btmtk_cif_allocate_memory failed!");
+ goto end;
+ }
+
+ err = btmtk_main_cif_initialize(bdev, HCI_USB);
+ if (err < 0) {
+ if (err == -EIO) {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed, do chip reset!!!");
+ goto end;
+ } else {
+ BTMTK_ERR("[ERR] btmtk_main_cif_initialize failed!");
+ goto free_mem;
+ }
+ }
+
+ /* only usb interface need this callback to allocate isoc trx endpoint
+ * There is no need for other interface such as sdio to use this function
+ */
+ bdev->hdev->notify = btusb_notify;
+
+ SET_HCIDEV_DEV(bdev->hdev, &cif_dev->intf->dev);
+
+#if CFG_SUPPORT_HW_DVT
+ /* We don't need to download patch during bring-up stage. */
+ BTMTK_INFO("SKIP downlaod patch");
+#else
+ if (BTMTK_IS_BT_0_INTF(ifnum_base))
+ err = btmtk_load_rom_patch(bdev);
+ else
+ BTMTK_INFO("interface = %d, don't download patch", ifnum_base);
+#endif /* CFG_SUPPORT_HW_DVT */
+
+ if (err < 0) {
+ BTMTK_ERR("btmtk load rom patch failed, do chip reset!!!");
+ goto end;
+ }
+
+ /* For reset */
+ btmtk_cif_write_uhw_register(bdev, EP_RST_OPT, 0x00010001);
+
+ /* Interface numbers are hardcoded in the specification */
+ if (BTMTK_IS_BT_0_INTF(ifnum_base)) {
+ cif_dev->isoc = usb_ifnum_to_if(cif_dev->udev, 1);
+
+ BTMTK_INFO("set interface number 2 for iso ");
+ cif_dev->iso_channel = usb_ifnum_to_if(cif_dev->udev, 2);
+ usb_set_interface(cif_dev->udev, 2, 1);
+ if (cif_dev->iso_channel) {
+ for (i = 0; i < cif_dev->iso_channel->cur_altsetting->desc.bNumEndpoints; i++) {
+ ep_desc = &cif_dev->iso_channel->cur_altsetting->endpoint[i].desc;
+
+ if (!cif_dev->intr_iso_tx_ep && usb_endpoint_is_int_out(ep_desc)) {
+ cif_dev->intr_iso_tx_ep = ep_desc;
+ BTMTK_INFO("intr_iso_tx_ep i = %d\t"
+ "Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress,
+ intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+
+ if (!cif_dev->intr_iso_rx_ep && usb_endpoint_is_int_in(ep_desc)) {
+ cif_dev->intr_iso_rx_ep = ep_desc;
+ BTMTK_INFO("intr_iso_rx_ep i = %d\t"
+ "Endpoints 0x%02X, number_of_endpoints=%d",
+ i, ep_desc->bEndpointAddress,
+ intf->cur_altsetting->desc.bNumEndpoints);
+ continue;
+ }
+ }
+
+ err = usb_driver_claim_interface(&btusb_driver,
+ cif_dev->iso_channel, bdev);
+ if (err < 0)
+ goto deinit1;
+ }
+ } else if (BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ BTMTK_INFO("interface number = 3, set interface number 4");
+ cif_dev->isoc = usb_ifnum_to_if(cif_dev->udev, 4);
+ }
+
+ if (cif_dev->isoc) {
+ err = usb_driver_claim_interface(&btusb_driver,
+ cif_dev->isoc, bdev);
+ if (err < 0)
+ goto deinit1;
+ }
+
+ /* dongle_index - 1 since BT1 is in same interface */
+ if (BTMTK_IS_BT_1_INTF(ifnum_base))
+ bdev->dongle_index--;
+ BTMTK_DBG("%s: bdev->dongle_index = %d ", __func__, bdev->dongle_index);
+
+ usb_set_intfdata(intf, bdev);
+
+ err = btmtk_woble_initialize(bdev, &cif_dev->bt_woble);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_main_woble_initialize failed, do chip reset!!!");
+ goto end;
+ }
+
+ btmtk_woble_wake_unlock(bdev);
+
+#if CFG_SUPPORT_BLUEZ
+ err = btmtk_send_init_cmds(bdev);
+ if (err < 0) {
+ BTMTK_ERR("%s, btmtk_send_init_cmds failed, err = %d", __func__, err);
+ goto free_setting;
+ }
+#endif /* CFG_SUPPORT_BLUEZ */
+
+ err = btmtk_register_hci_device(bdev);
+ if (err < 0) {
+ BTMTK_ERR("btmtk_register_hci_device failed!");
+ goto free_setting;
+ }
+
+ goto end;
+
+free_setting:
+ btmtk_woble_uninitialize(&cif_dev->bt_woble);
+deinit1:
+ btmtk_main_cif_uninitialize(bdev, HCI_USB);
+free_mem:
+ btmtk_cif_free_memory(cif_dev);
+end:
+ atomic_set(&bmain_info->chip_reset, BTMTK_RESET_DONE);
+ return 0;
+}
+
+static void btusb_disconnect(struct usb_interface *intf)
+{
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+ struct hci_dev *hdev;
+
+ BTMTK_DBG("intf %p", intf);
+ bdev = usb_get_intfdata(intf);
+ if (!bdev) {
+ BTMTK_WARN("%s: bdev is NULL!", __func__);
+ return;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (!cif_dev) {
+ BTMTK_WARN("%s: cif_dev is NULL!", __func__);
+ return;
+ }
+
+ hdev = bdev->hdev;
+ usb_set_intfdata(cif_dev->intf, NULL);
+
+ if (cif_dev->isoc)
+ usb_set_intfdata(cif_dev->isoc, NULL);
+
+ if (cif_dev->iso_channel)
+ usb_set_intfdata(cif_dev->iso_channel, NULL);
+
+ if (intf == cif_dev->intf) {
+ if (cif_dev->isoc)
+ usb_driver_release_interface(&btusb_driver, cif_dev->isoc);
+ if (cif_dev->iso_channel)
+ usb_driver_release_interface(&btusb_driver, cif_dev->iso_channel);
+ } else if (intf == cif_dev->isoc) {
+ usb_driver_release_interface(&btusb_driver, cif_dev->intf);
+ } else if (intf == cif_dev->iso_channel) {
+ usb_driver_release_interface(&btusb_driver, cif_dev->intf);
+ }
+
+ btmtk_main_cif_disconnect_notify(bdev, HCI_USB);
+ btmtk_woble_uninitialize(&cif_dev->bt_woble);
+ btmtk_cif_free_memory(cif_dev);
+
+}
+
+#ifdef CONFIG_PM
+static int btusb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct btmtk_dev *bdev = usb_get_intfdata(intf);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+#else
+ struct btmtk_woble *bt_woble = &cif_dev->bt_woble;
+#endif
+ int ret = 0;
+
+ BTMTK_DBG("intf %p", intf);
+
+ if (bdev->suspend_count++) {
+ BTMTK_WARN("%s: Has suspended. suspend_count: %d end", __func__, bdev->suspend_count);
+ return 0;
+ }
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_suspend flow", __func__);
+#else
+ ret = btmtk_woble_suspend(bt_woble);
+ if (ret < 0)
+ BTMTK_ERR("%s: btmtk_woble_suspend return fail %d", __func__, ret);
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ spin_lock_irq(&bdev->txlock);
+ if (!(PMSG_IS_AUTO(message) && bdev->tx_in_flight)) {
+ set_bit(BTUSB_SUSPENDING, &bdev->flags);
+ spin_unlock_irq(&bdev->txlock);
+ } else {
+ spin_unlock_irq(&bdev->txlock);
+ bdev->suspend_count--;
+ return -EBUSY;
+ }
+
+ cancel_work_sync(&bdev->work);
+
+ btusb_stop_traffic(cif_dev);
+ usb_kill_anchored_urbs(&cif_dev->tx_anchor);
+
+ BTMTK_INFO("%s end, suspend_count = %d", __func__, bdev->suspend_count);
+
+ return ret;
+}
+
+static int btusb_resume(struct usb_interface *intf)
+{
+ struct btmtk_dev *bdev = usb_get_intfdata(intf);
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ struct hci_dev *hdev = bdev->hdev;
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+#else
+ struct btmtk_woble *bt_woble = &cif_dev->bt_woble;
+#endif
+ int err = 0;
+ unsigned int ifnum_base = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ BTMTK_INFO("%s begin", __func__);
+
+ if (--bdev->suspend_count) {
+ BTMTK_WARN("%s: bdev->suspend_count %d, return 0", __func__,
+ bdev->suspend_count);
+ return 0;
+ }
+
+ /* need to remove it when BT off, need support woble case*/
+ /* if (!test_bit(HCI_RUNNING, &hdev->flags)) {
+ * BTMTK_WARN("%s: hdev flags is not hci running. return", __func__);
+ * goto done;
+ * }
+ */
+
+ /* when BT off, BTUSB_INTR_RUNNING will be clear,
+ * so we need to start traffic in btmtk_woble_resume when BT off
+ */
+ if (test_bit(BTUSB_INTR_RUNNING, &bdev->flags)) {
+ err = btusb_submit_intr_urb(hdev, GFP_NOIO);
+ if (err < 0) {
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ goto done;
+ }
+
+ if (is_mt7961(bdev->chip_id) && BTMTK_IS_BT_0_INTF(ifnum_base)) {
+ BTMTK_INFO("%s 7961 submit urb\n", __func__);
+ if (cif_dev->reset_intr_ep) {
+ err = btusb_submit_intr_reset_urb(hdev, GFP_KERNEL);
+ if (err < 0) {
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ goto done;
+ }
+ } else
+ BTMTK_INFO("%s, reset_intr_ep missing, don't submit_intr_reset_urb!",
+ __func__);
+
+ if (cif_dev->intr_iso_rx_ep) {
+ err = btusb_submit_intr_ble_isoc_urb(hdev, GFP_KERNEL);
+ if (err < 0) {
+ usb_kill_anchored_urbs(&cif_dev->ble_isoc_anchor);
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ goto done;
+ }
+ } else
+ BTMTK_INFO("%s, intr_iso_rx_ep missing, don't submit_intr_ble_isoc_urb!",
+ __func__);
+ }
+ }
+
+ if (test_bit(BTUSB_BULK_RUNNING, &bdev->flags)) {
+ err = btusb_submit_bulk_urb(hdev, GFP_NOIO);
+ if (err < 0) {
+ clear_bit(BTUSB_BULK_RUNNING, &bdev->flags);
+ goto done;
+ }
+
+ btusb_submit_bulk_urb(hdev, GFP_NOIO);
+ }
+
+ if (test_bit(BTUSB_ISOC_RUNNING, &bdev->flags)) {
+ if (btusb_submit_isoc_urb(hdev, GFP_NOIO) < 0)
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ else
+ btusb_submit_isoc_urb(hdev, GFP_NOIO);
+ }
+
+ if (test_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags)) {
+ if (btusb_submit_intr_ble_isoc_urb(hdev, GFP_NOIO) < 0)
+ clear_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags);
+ else
+ btusb_submit_intr_ble_isoc_urb(hdev, GFP_NOIO);
+ }
+
+ spin_lock_irq(&bdev->txlock);
+ clear_bit(BTUSB_SUSPENDING, &bdev->flags);
+ spin_unlock_irq(&bdev->txlock);
+ schedule_work(&bdev->work);
+
+#if CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT
+ BTMTK_INFO("%s: SKIP Driver woble_resume flow", __func__);
+#else
+ err = btmtk_woble_resume(bt_woble);
+ if (err < 0) {
+ BTMTK_ERR("%s: btmtk_woble_resume return fail %d", __func__, err);
+ goto done;
+ }
+#endif /* CFG_SUPPORT_DVT || CFG_SUPPORT_HW_DVT */
+
+ BTMTK_INFO("%s end", __func__);
+
+ return 0;
+
+done:
+ spin_lock_irq(&bdev->txlock);
+ clear_bit(BTUSB_SUSPENDING, &bdev->flags);
+ spin_unlock_irq(&bdev->txlock);
+
+ return err;
+}
+#endif
+
+static int btmtk_cif_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret = -1;
+ int cif_event = 0;
+ unsigned int ifnum_base;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ /* Mediatek Driver Version */
+ BTMTK_INFO("%s: MTK BT Driver Version : %s", __func__, VERSION);
+
+ DUMP_TIME_STAMP("probe_start");
+ /* USB interface only.
+ * USB will need to identify thru descriptor's interface numbering.
+ */
+ ifnum_base = intf->cur_altsetting->desc.bInterfaceNumber;
+ BTMTK_DBG("intf %p id %p, interfacenum = %d", intf, id, ifnum_base);
+
+ /* interface numbers are hardcoded in the spec */
+ if (ifnum_base != BT0_MCU_INTERFACE_NUM &&
+ ifnum_base != BT1_MCU_INTERFACE_NUM)
+ return -ENODEV;
+
+ /* Retrieve priv data and set to interface structure */
+ bdev = btmtk_get_dev();
+ if (!bdev) {
+ BTMTK_WARN("%s: bdev is NULL!", __func__);
+ return -ENODEV;
+ }
+ usb_set_intfdata(intf, bdev);
+ bdev->cif_dev = &g_usb_dev[bdev->dongle_index][intf_to_idx[ifnum_base]];
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_PROBE;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s intf[%d] priv setting is NULL", __func__, ifnum_base);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btusb_probe(intf, id);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+
+ DUMP_TIME_STAMP("probe_end");
+ return ret;
+}
+
+static void btmtk_cif_disconnect(struct usb_interface *intf)
+{
+ int cif_event = 0;
+ unsigned int ifnum_base;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+ struct btmtk_usb_dev *cif_dev = NULL;
+
+ BTMTK_WARN("%s: begin", __func__);
+ BTMTK_CIF_GET_DEV_PRIV(bdev, intf, ifnum_base);
+
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_DISCONNECT;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s intf[%d] priv setting is NULL", __func__, ifnum_base);
+ return;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (!cif_dev) {
+ BTMTK_WARN("%s: cif_dev is NULL!", __func__);
+ return;
+ }
+
+ clear_bit(BTUSB_INTR_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_BULK_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_BLE_ISOC_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_WMT_RUNNING, &bdev->flags);
+ clear_bit(BTUSB_SUSPENDING, &bdev->flags);
+
+ btusb_stop_traffic(cif_dev);
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ btusb_disconnect(intf);
+
+ /* Set End/Error state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+}
+
+#ifdef CONFIG_PM
+static int btmtk_cif_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ int ret = 0;
+ unsigned int ifnum_base;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+ int state = BTMTK_STATE_INIT;
+
+ BTMTK_INFO("%s, enter", __func__);
+ BTMTK_CIF_GET_DEV_PRIV(bdev, intf, ifnum_base);
+
+ state = btmtk_get_chip_state(bdev);
+ /* Retrieve current HIF event state */
+ if (state == BTMTK_STATE_FW_DUMP) {
+ BTMTK_WARN("%s: FW dumping ongoing, don't dos suspend flow!!!", __func__);
+ cif_event = HIF_EVENT_FW_DUMP;
+ } else
+ cif_event = HIF_EVENT_SUSPEND;
+
+ if (BTMTK_IS_BT_0_INTF(ifnum_base) || BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s intf[%d] priv setting is NULL", __func__, ifnum_base);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btusb_suspend(intf, message);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ } else
+ BTMTK_INFO("%s, interface num is for isoc interface, do't do suspend!", __func__);
+
+ BTMTK_INFO("%s, end. ret = %d", __func__, ret);
+ return ret;
+}
+
+static int btmtk_cif_resume(struct usb_interface *intf)
+{
+ int ret = 0;
+ unsigned int ifnum_base;
+ int cif_event = 0;
+ struct btmtk_cif_state *cif_state = NULL;
+ struct btmtk_dev *bdev = NULL;
+
+ BTMTK_INFO("%s, enter", __func__);
+ BTMTK_CIF_GET_DEV_PRIV(bdev, intf, ifnum_base);
+
+ if (BTMTK_IS_BT_0_INTF(ifnum_base) || BTMTK_IS_BT_1_INTF(ifnum_base)) {
+ /* Retrieve current HIF event state */
+ cif_event = HIF_EVENT_RESUME;
+ if (BTMTK_CIF_IS_NULL(bdev, cif_event)) {
+ /* Error */
+ BTMTK_WARN("%s intf[%d] priv setting is NULL", __func__, ifnum_base);
+ return -ENODEV;
+ }
+
+ cif_state = &bdev->cif_state[cif_event];
+
+ /* Set Entering state */
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_enter);
+
+ /* Do HIF events */
+ ret = btusb_resume(intf);
+
+ /* Set End/Error state */
+ if (ret == 0)
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_end);
+ else
+ btmtk_set_chip_state((void *)bdev, cif_state->ops_error);
+ } else
+ BTMTK_INFO("%s, interface num is for isoc interface, do't do resume!", __func__);
+
+ BTMTK_INFO("%s, end. ret = %d", __func__, ret);
+ return ret;
+}
+#endif /* CONFIG_PM */
+
+#if !BT_DISABLE_RESET_RESUME
+static int btmtk_cif_reset_resume(struct usb_interface *intf)
+{
+ BTMTK_INFO("%s: Call resume directly", __func__);
+ return btmtk_cif_resume(intf);
+}
+#endif
+
+static struct usb_driver btusb_driver = {
+ .name = "btusb",
+ .probe = btmtk_cif_probe,
+ .disconnect = btmtk_cif_disconnect,
+#ifdef CONFIG_PM
+ .suspend = btmtk_cif_suspend,
+ .resume = btmtk_cif_resume,
+#endif
+#if !BT_DISABLE_RESET_RESUME
+ .reset_resume = btmtk_cif_reset_resume,
+#endif
+ .id_table = btusb_table,
+ .supports_autosuspend = 1,
+ .disable_hub_initiated_lpm = 1,
+};
+
+int btmtk_cif_register(void)
+{
+ int retval = 0;
+ struct hif_hook_ptr hook;
+
+ BTMTK_INFO("%s", __func__);
+
+ /* register system power off callback function. */
+ do {
+ typedef void (*func_ptr) (int (*f) (void));
+ char *func_name = "RegisterPdwncCallback";
+ func_ptr pFunc = (func_ptr) btmtk_kallsyms_lookup_name(func_name);
+
+ if (pFunc) {
+ BTMTK_INFO("%s: Register Pdwnc callback success.", __func__);
+ pFunc(&btmtk_usb_enter_standby);
+ } else
+ BTMTK_WARN("%s: No Exported Func Found [%s], just skip!", __func__, func_name);
+ } while (0);
+
+ memset(&hook, 0, sizeof(struct hif_hook_ptr));
+ hook.open = btmtk_usb_open;
+ hook.close = btmtk_usb_close;
+ hook.reg_read = btmtk_usb_read_register;
+ hook.reg_write = btmtk_usb_write_register;
+ hook.send_cmd = btmtk_usb_send_cmd;
+ hook.send_and_recv = btmtk_usb_send_and_recv;
+ hook.event_filter = btmtk_usb_event_filter;
+ hook.subsys_reset = btmtk_usb_subsys_reset;
+ hook.whole_reset = btmtk_usb_toggle_rst_pin;
+ hook.chip_reset_notify = btmtk_usb_chip_reset_notify;
+ hook.cif_mutex_lock = btmtk_usb_cif_mutex_lock;
+ hook.cif_mutex_unlock = btmtk_usb_cif_mutex_unlock;
+ hook.dl_dma = btmtk_usb_load_fw_patch_using_dma;
+ hook.dump_debug_sop = btmtk_usb_dump_debug_sop;
+ hook.enter_standby = btmtk_usb_enter_standby;
+ btmtk_reg_hif_hook(&hook);
+
+ retval = usb_register(&btusb_driver);
+ if (retval)
+ BTMTK_ERR("*** USB registration fail(%d)! ***", retval);
+ else
+ BTMTK_INFO("%s, usb registration success!", __func__);
+ return retval;
+}
+
+int btmtk_cif_deregister(void)
+{
+ BTMTK_INFO("%s", __func__);
+ usb_deregister(&btusb_driver);
+ BTMTK_INFO("%s: Done", __func__);
+ return 0;
+}
+
+static int btmtk_cif_allocate_memory(struct btmtk_usb_dev *cif_dev)
+{
+ int err = -1;
+
+ BTMTK_INFO("%s Begin", __func__);
+
+ if (cif_dev->o_usb_buf == NULL) {
+ cif_dev->o_usb_buf = kzalloc(HCI_MAX_COMMAND_SIZE, GFP_KERNEL);
+ if (!cif_dev->o_usb_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->o_usb_buf)", __func__);
+ goto end;
+ }
+ }
+
+ if (cif_dev->urb_intr_buf == NULL) {
+ cif_dev->urb_intr_buf = kzalloc(URB_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->urb_intr_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->urb_intr_buf)", __func__);
+ goto err2;
+ }
+ }
+ if (cif_dev->urb_bulk_buf == NULL) {
+ cif_dev->urb_bulk_buf = kzalloc(URB_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->urb_bulk_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->urb_bulk_buf)", __func__);
+ goto err1;
+ }
+ }
+ if (cif_dev->urb_ble_isoc_buf == NULL) {
+ cif_dev->urb_ble_isoc_buf = kzalloc(URB_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!cif_dev->urb_ble_isoc_buf) {
+ BTMTK_ERR("%s: alloc memory fail (bdev->urb_ble_isoc_buf)", __func__);
+ goto err0;
+ }
+ }
+
+ BTMTK_INFO("%s End", __func__);
+ return 0;
+
+err0:
+ kfree(cif_dev->urb_bulk_buf);
+ cif_dev->urb_bulk_buf = NULL;
+err1:
+ kfree(cif_dev->urb_intr_buf);
+ cif_dev->urb_intr_buf = NULL;
+err2:
+ kfree(cif_dev->o_usb_buf);
+ cif_dev->o_usb_buf = NULL;
+end:
+ return err;
+}
+
+static void btmtk_cif_free_memory(struct btmtk_usb_dev *cif_dev)
+{
+ if (!cif_dev) {
+ BTMTK_ERR("%s: bdev is NULL!", __func__);
+ return;
+ }
+
+ kfree(cif_dev->o_usb_buf);
+ cif_dev->o_usb_buf = NULL;
+
+ kfree(cif_dev->urb_intr_buf);
+ cif_dev->urb_intr_buf = NULL;
+
+ kfree(cif_dev->urb_bulk_buf);
+ cif_dev->urb_bulk_buf = NULL;
+
+ kfree(cif_dev->urb_ble_isoc_buf);
+ cif_dev->urb_ble_isoc_buf = NULL;
+
+ memset(cif_dev, 0, sizeof(struct btmtk_usb_dev));
+
+ BTMTK_INFO("%s: Success", __func__);
+}
+
+static int btmtk_cif_write_uhw_register(struct btmtk_dev *bdev, u32 reg, u32 val)
+{
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int ret = -1;
+ __le16 reg_high;
+ __le16 reg_low;
+ u8 reset_buf[4];
+
+ reg_high = ((reg >> 16) & 0xffff);
+ reg_low = (reg & 0xffff);
+
+ reset_buf[0] = (val & 0x00ff);
+ reset_buf[1] = ((val >> 8) & 0x00ff);
+ reset_buf[2] = ((val >> 16) & 0x00ff);
+ reset_buf[3] = ((val >> 24) & 0x00ff);
+
+ memcpy(cif_dev->o_usb_buf, reset_buf, sizeof(reset_buf));
+ ret = usb_control_msg(cif_dev->udev, usb_sndctrlpipe(cif_dev->udev, 0),
+ 0x02, /* bRequest */
+ 0x5E, /* bRequestType */
+ reg_high, /* wValue */
+ reg_low, /* wIndex */
+ cif_dev->o_usb_buf,
+ sizeof(reset_buf), USB_CTRL_IO_TIMO);
+
+ BTMTK_DBG("%s: high=%x, reg_low=%x, val=%x", __func__, reg_high, reg_low, val);
+ BTMTK_DBG("%s: reset_buf = %x %x %x %x", __func__, reset_buf[3], reset_buf[2], reset_buf[1], reset_buf[0]);
+
+ if (ret < 0) {
+ val = 0xffffffff;
+ BTMTK_ERR("%s: error(%d), reg=%x, value=%x", __func__, ret, reg, val);
+ return ret;
+ }
+ return 0;
+}
+
+static int btmtk_cif_read_uhw_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int ret = -1;
+ __le16 reg_high;
+ __le16 reg_low;
+
+ reg_high = ((reg >> 16) & 0xffff);
+ reg_low = (reg & 0xffff);
+
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+ ret = usb_control_msg(cif_dev->udev, usb_rcvctrlpipe(cif_dev->udev, 0),
+ 0x01, /* bRequest */
+ 0xDE, /* bRequestType */
+ reg_high, /* wValue */
+ reg_low, /* wIndex */
+ bdev->io_buf,
+ 4, USB_CTRL_IO_TIMO);
+
+ if (ret < 0) {
+ *val = 0xffffffff;
+ BTMTK_ERR("%s: error(%d), reg=%x, value=0x%08x", __func__, ret, reg, *val);
+ return ret;
+ }
+
+ memmove(val, bdev->io_buf, sizeof(u32));
+ *val = le32_to_cpu(*val);
+
+ BTMTK_DBG("%s: reg=%x, value=0x%08x", __func__, reg, *val);
+
+ return 0;
+}
+
+static int btmtk_usb_read_register(struct btmtk_dev *bdev, u32 reg, u32 *val)
+{
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int ret = 0;
+ __le16 reg_high;
+ __le16 reg_low;
+
+ reg_high = ((reg >> 16) & 0xffff);
+ reg_low = (reg & 0xffff);
+
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+ ret = usb_control_msg(cif_dev->udev, usb_rcvctrlpipe(cif_dev->udev, 0),
+ 0x63, /* bRequest */
+ DEVICE_VENDOR_REQUEST_IN, /* bRequestType */
+ reg_high, /* wValue */
+ reg_low, /* wIndex */
+ bdev->io_buf,
+ sizeof(u32), USB_CTRL_IO_TIMO);
+
+ if (ret < 0) {
+ *val = 0xffffffff;
+ BTMTK_ERR("%s: error(%d), reg=%x, value=%x", __func__, ret, reg, *val);
+ btmtk_reset_trigger(bdev);
+ return ret;
+ }
+
+ memmove(val, bdev->io_buf, sizeof(u32));
+ *val = le32_to_cpu(*val);
+
+ return 0;
+}
+
+static int btmtk_usb_write_register(struct btmtk_dev *bdev, u32 reg, u32 val)
+{
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ int ret = -1;
+ __le16 reg_high;
+ __le16 reg_low;
+ u8 buf[4];
+
+ reg_high = ((reg >> 16) & 0xffff);
+ reg_low = (reg & 0xffff);
+
+ buf[0] = 0;
+ buf[1] = 0;
+ buf[2] = (val & 0x00ff);
+ buf[3] = ((val >> 8) & 0x00ff);
+
+ memcpy(cif_dev->o_usb_buf, buf, sizeof(buf));
+ ret = usb_control_msg(cif_dev->udev, usb_sndctrlpipe(cif_dev->udev, 0),
+ 0x66, /* bRequest */
+ 0x40, /* bRequestType */
+ reg_high, /* wValue */
+ reg_low, /* wIndex */
+ cif_dev->o_usb_buf,
+ sizeof(buf), USB_CTRL_IO_TIMO);
+
+ BTMTK_DBG("%s: buf = %x %x %x %x", __func__, buf[3], buf[2], buf[1], buf[0]);
+
+ if (ret < 0) {
+ val = 0xffffffff;
+ BTMTK_ERR("%s: error(%d), reg=%x, value=%x", __func__, ret, reg, val);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void btmtk_cif_load_rom_patch_complete(struct urb *urb)
+{
+ struct completion *sent_to_mcu_done = (struct completion *)urb->context;
+
+ complete(sent_to_mcu_done);
+}
+
+int btmtk_cif_send_control_out(struct btmtk_dev *bdev, struct sk_buff *skb, int delay, int retry)
+{
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int ret = 0;
+ unsigned int ifnum_base;
+
+ if (bdev == NULL || bdev->hdev == NULL || bdev->io_buf == NULL || skb == NULL ||
+ skb->len > HCI_MAX_COMMAND_SIZE || skb->len <= 0) {
+ BTMTK_ERR("%s: incorrect parameter", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (cif_dev->udev == NULL || cif_dev->o_usb_buf == NULL) {
+ BTMTK_ERR("%s: cif_dev is invalid", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+
+ /* send wmt command */
+ memcpy(cif_dev->o_usb_buf, skb->data + 1, skb->len - 1);
+ BTMTK_INFO_RAW(skb->data + 1, skb->len - 1, "%s: cmd:", __func__);
+ if (BTMTK_IS_BT_0_INTF(ifnum_base))
+ ret = usb_control_msg(cif_dev->udev, usb_sndctrlpipe(cif_dev->udev, 0),
+ 0x01, DEVICE_CLASS_REQUEST_OUT, 0x30, 0x00, (void *)cif_dev->o_usb_buf,
+ skb->len - 1, USB_CTRL_IO_TIMO);
+ else if (BTMTK_IS_BT_1_INTF(ifnum_base))
+ ret = usb_control_msg(cif_dev->udev, usb_sndctrlpipe(cif_dev->udev, 0),
+ 0x00, 0x21, 0x00, 0x03, (void *)cif_dev->o_usb_buf, skb->len - 1, USB_CTRL_IO_TIMO);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s: command send failed(%d)", __func__, ret);
+ goto exit;
+ }
+
+ /* only when ret = 0 need to free skb; when ret < 0, it will be free in btmtk_main_send_cmd */
+ kfree_skb(skb);
+ skb = NULL;
+
+exit:
+ return ret;
+}
+
+static int btmtk_cif_send_bulk_out(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ int ret = 0;
+ struct urb *urb;
+ unsigned int pipe;
+ struct completion sent_to_mcu_done;
+ void *buf;
+ struct btmtk_usb_dev *cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ /* why need to alloc dma buffer??*/
+ buf = usb_alloc_coherent(cif_dev->udev, UPLOAD_PATCH_UNIT, GFP_KERNEL, &urb->transfer_dma);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto error1;
+ }
+ init_completion(&sent_to_mcu_done);
+
+ pipe = usb_sndbulkpipe(cif_dev->udev, cif_dev->bulk_tx_ep->bEndpointAddress);
+
+ memcpy(buf, skb->data + 1, skb->len - 1);
+ usb_fill_bulk_urb(urb,
+ cif_dev->udev,
+ pipe,
+ buf,
+ skb->len - 1,
+ (usb_complete_t)btmtk_cif_load_rom_patch_complete,
+ &sent_to_mcu_done);
+
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret < 0) {
+ BTMTK_ERR("%s: submit urb failed (%d)", __func__, ret);
+ goto error0;
+ }
+
+ if (!wait_for_completion_timeout
+ (&sent_to_mcu_done, msecs_to_jiffies(3000))) {
+ usb_kill_urb(urb);
+ BTMTK_ERR("%s: upload rom_patch timeout", __func__);
+ ret = -ETIME;
+ goto error0;
+ }
+
+ kfree_skb(skb);
+ skb = NULL;
+error0:
+ usb_free_coherent(cif_dev->udev, UPLOAD_PATCH_UNIT, buf, urb->transfer_dma);
+error1:
+ usb_free_urb(urb);
+exit:
+ return ret;
+}
+
+int btmtk_usb_send_cmd(struct btmtk_dev *bdev, struct sk_buff *skb,
+ int delay, int retry, int pkt_type)
+{
+ int ret = -1;
+
+ if (pkt_type == BTMTK_TX_CMD_FROM_DRV) {
+ /* handle wmt cmd from driver */
+ ret = btmtk_cif_send_control_out(bdev, skb, delay, retry);
+ } else if (pkt_type == BTMTK_TX_ACL_FROM_DRV) {
+ /* bulk out for load rom patch*/
+ ret = btmtk_cif_send_bulk_out(bdev, skb);
+ } else if (pkt_type == BTMTK_TX_PKT_FROM_HOST) {
+ /* handle hci cmd and acl pkt from host, handle hci cmd from driver */
+ ret = btusb_send_frame(bdev->hdev, skb);
+ }
+
+ return ret;
+}
+
+static int btmtk_cif_recv_evt(struct btmtk_dev *bdev, int delay, int retry)
+{
+ struct btmtk_usb_dev *cif_dev = NULL;
+ int ret = -1; /* if successful, 0 */
+ unsigned int ifnum_base;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev == NULL!\n", __func__);
+ return ret;
+ }
+
+ cif_dev = (struct btmtk_usb_dev *)bdev->cif_dev;
+ if (!cif_dev->udev || !bdev->hdev) {
+ BTMTK_ERR("%s: invalid parameters!\n", __func__);
+ return ret;
+ }
+
+ ifnum_base = cif_dev->intf->cur_altsetting->desc.bInterfaceNumber;
+get_response_again:
+ /* us delay */
+ usleep_range(delay * TIME_MULTIPL, delay * TIME_MULTIPL + TIME_US_OFFSET_RANGE);
+
+ /* check WMT event */
+ memset(bdev->io_buf, 0, IO_BUF_SIZE);
+ bdev->io_buf[0] = HCI_EVENT_PKT;
+ if (BTMTK_IS_BT_0_INTF(ifnum_base))
+ ret = usb_control_msg(cif_dev->udev, usb_rcvctrlpipe(cif_dev->udev, 0),
+ 0x01, DEVICE_VENDOR_REQUEST_IN, 0x30, 0x00, bdev->io_buf + 1,
+ HCI_USB_IO_BUF_SIZE, USB_CTRL_IO_TIMO);
+ else if (BTMTK_IS_BT_1_INTF(ifnum_base))
+ ret = usb_control_msg(cif_dev->udev, usb_rcvctrlpipe(cif_dev->udev, 0),
+ 0x01, 0xA1, 0x30, 0x03, bdev->io_buf + 1, HCI_USB_IO_BUF_SIZE,
+ USB_CTRL_IO_TIMO);
+
+ if (ret < 0) {
+ BTMTK_ERR("%s: event get failed(%d)", __func__, ret);
+ return ret;
+ }
+
+ if (ret > 0) {
+ BTMTK_DBG_RAW(bdev->io_buf, ret + 1, "%s OK: EVT:", __func__);
+ return ret + 1; /* return read length */
+ } else if (retry > 0) {
+ BTMTK_WARN("%s: Trying to get response... (%d)", __func__, retry);
+ retry--;
+ goto get_response_again;
+ } else
+ BTMTK_ERR("%s NG: do not got response:(%d)", __func__, ret);
+
+ return -1;
+}
+
+int btmtk_usb_send_and_recv(struct btmtk_dev *bdev,
+ struct sk_buff *skb,
+ const uint8_t *event, const int event_len,
+ int delay, int retry, int pkt_type)
+{
+ unsigned long comp_event_timo = 0, start_time = 0;
+ int ret = 0;
+
+ if (bdev == NULL) {
+ BTMTK_ERR("%s: bdev == NULL!\n", __func__);
+ ret = -1;
+ return ret;
+ }
+
+ if ((pkt_type == BTMTK_TX_CMD_FROM_DRV || pkt_type == BTMTK_TX_ACL_FROM_DRV)) {
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send, len = %d", __func__, skb->len);
+
+ ret = btmtk_usb_send_cmd(bdev, skb, delay, retry, pkt_type);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_usb_send_cmd failed!!", __func__);
+ goto fw_assert;
+ }
+
+ if (event && event_len > 0) {
+ bdev->recv_evt_len = btmtk_cif_recv_evt(bdev, delay, retry);
+ if (bdev->recv_evt_len < 0) {
+ BTMTK_ERR("%s btmtk_cif_recv_evt failed!!", __func__);
+ ret = -ERRNUM;
+ goto fw_assert;
+ }
+
+ if (bdev->io_buf && bdev->recv_evt_len >= event_len) {
+ if (memcmp(bdev->io_buf, event, event_len) == 0) {
+ ret = 0;
+ goto exit;
+ }
+ }
+ BTMTK_INFO("%s compare fail\n", __func__);
+ BTMTK_INFO_RAW(event, event_len, "%s: event_need_compare:", __func__);
+ BTMTK_INFO_RAW(bdev->io_buf, bdev->recv_evt_len, "%s: RCV:", __func__);
+ ret = -ERRNUM;
+ } else {
+ ret = 0;
+ }
+ } else {
+ if (event) {
+ if (event_len > EVENT_COMPARE_SIZE) {
+ BTMTK_ERR("%s, event_len (%d) > EVENT_COMPARE_SIZE(%d), error",
+ __func__, event_len, EVENT_COMPARE_SIZE);
+ ret = -1;
+ goto exit;
+ }
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE;
+ memcpy(event_need_compare, event + 1, event_len - 1);
+ event_need_compare_len = event_len - 1;
+
+ start_time = jiffies;
+ /* check hci event /wmt event for SDIO/UART interface, check hci
+ * event for USB interface
+ */
+ comp_event_timo = jiffies + msecs_to_jiffies(WOBLE_COMP_EVENT_TIMO);
+ BTMTK_INFO("event_need_compare_len %d, event_compare_status %d",
+ event_need_compare_len, event_compare_status);
+ } else {
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ }
+
+ BTMTK_DBG_RAW(skb->data, skb->len, "%s, send, len = %d", __func__, skb->len);
+
+ ret = btmtk_usb_send_cmd(bdev, skb, delay, retry, pkt_type);
+ if (ret < 0) {
+ BTMTK_ERR("%s btmtk_sdio_send_cmd failed!!", __func__);
+ goto fw_assert;
+ }
+
+ do {
+ /* check if event_compare_success */
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS) {
+ ret = 0;
+ break;
+ }
+ usleep_range(10, 100);
+ } while (time_before(jiffies, comp_event_timo));
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE) {
+ BTMTK_ERR("%s wait expect event timeout!!", __func__);
+ ret = -ERRNUM;
+ goto fw_assert;
+ }
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_NOTHING_NEED_COMPARE;
+ }
+ goto exit;
+fw_assert:
+ btmtk_send_assert_cmd(bdev);
+exit:
+ return ret;
+}
+
+void btmtk_usb_chip_reset_notify(struct btmtk_dev *bdev)
+{
+ cancel_work_sync(&bdev->work);
+ cancel_work_sync(&bdev->waker);
+}
+
+int btmtk_usb_event_filter(struct btmtk_dev *bdev, struct sk_buff *skb)
+{
+ u8 read_address_event[READ_ADDRESS_EVT_HDR_LEN] = {0x04, 0x0E, 0x0A, 0x01, 0x09, 0x10, 0x00 };
+
+ if (event_compare_status == BTMTK_EVENT_COMPARE_STATE_NEED_COMPARE &&
+ skb->len >= event_need_compare_len) {
+ if (memcmp(skb->data, &read_address_event[1], READ_ADDRESS_EVT_HDR_LEN - 1) == 0
+ && (skb->len == (READ_ADDRESS_EVT_HDR_LEN - 1 + BD_ADDRESS_SIZE))) {
+ memcpy(bdev->bdaddr, &skb->data[READ_ADDRESS_EVT_PAYLOAD_OFFSET - 1], BD_ADDRESS_SIZE);
+ BTMTK_INFO("%s: GET BDADDR = "MACSTR, __func__, MAC2STR(bdev->bdaddr));
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ } else if (memcmp(skb->data, event_need_compare,
+ event_need_compare_len) == 0) {
+ /* if it is wobx debug event, just print in kernel log, drop it
+ * by driver, don't send to stack
+ */
+ if (skb->data[0] == WOBLE_DEBUG_EVT_TYPE)
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: wobx debug log:", __func__);
+
+ /* If driver need to check result from skb, it can get from io_buf
+ * Such as chip_id, fw_version, etc.
+ */
+ bdev->io_buf[0] = bt_cb(skb)->pkt_type;
+ memcpy(&bdev->io_buf[1], skb->data, skb->len);
+
+ event_compare_status = BTMTK_EVENT_COMPARE_STATE_COMPARE_SUCCESS;
+ BTMTK_INFO("%s, compare success", __func__);
+ } else {
+ if (skb->data[0] != BLE_EVT_TYPE) {
+ /* Don't care BLE event */
+ BTMTK_INFO("%s compare fail", __func__);
+ BTMTK_INFO_RAW(event_need_compare, event_need_compare_len,
+ "%s: event_need_compare:", __func__);
+ BTMTK_INFO_RAW(skb->data, skb->len, "%s: skb->data:", __func__);
+ }
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/util/checkpatch.pl b/util/checkpatch.pl
new file mode 100755
index 0000000..cca8a1c
--- /dev/null
+++ b/util/checkpatch.pl
@@ -0,0 +1,6285 @@
+#!/usr/bin/perl -w
+# (c) 2001, Dave Jones. (the file handling bit)
+# (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit)
+# (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite)
+# (c) 2008-2010 Andy Whitcroft <apw@canonical.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+use strict;
+use POSIX;
+use File::Basename;
+use Cwd 'abs_path';
+use Term::ANSIColor qw(:constants);
+
+my $P = $0;
+my $D = dirname(abs_path($P));
+
+my $V = '0.32';
+
+use Getopt::Long qw(:config no_auto_abbrev);
+
+my $quiet = 0;
+my $tree = 1;
+my $chk_signoff = 1;
+my $chk_patch = 1;
+my $tst_only;
+my $emacs = 0;
+my $terse = 0;
+my $showfile = 0;
+my $file = 0;
+my $git = 0;
+my %git_commits = ();
+my $check = 0;
+my $check_orig = 0;
+my $summary = 1;
+my $mailback = 0;
+my $summary_file = 0;
+my $show_types = 0;
+my $list_types = 0;
+my $fix = 0;
+my $fix_inplace = 0;
+my $root;
+my %debug;
+my %camelcase = ();
+my %use_type = ();
+my @use = ();
+my %ignore_type = ();
+my @ignore = ();
+my $help = 0;
+my $configuration_file = ".checkpatch.conf";
+my $max_line_length = 80;
+my $ignore_perl_version = 0;
+my $minimum_perl_version = 5.10.0;
+my $min_conf_desc_length = 4;
+my $spelling_file = "$D/spelling.txt";
+my $codespell = 0;
+my $codespellfile = "/usr/share/codespell/dictionary.txt";
+my $conststructsfile = "$D/const_structs.checkpatch";
+my $color = 1;
+my $allow_c99_comments = 1;
+
+sub help {
+ my ($exitcode) = @_;
+
+ print << "EOM";
+Usage: $P [OPTION]... [FILE]...
+Version: $V
+
+Options:
+ -q, --quiet quiet
+ --no-tree run without a kernel tree
+ --no-signoff do not check for 'Signed-off-by' line
+ --patch treat FILE as patchfile (default)
+ --emacs emacs compile window format
+ --terse one line per report
+ --showfile emit diffed file position, not input file position
+ -g, --git treat FILE as a single commit or git revision range
+ single git commit with:
+ <rev>
+ <rev>^
+ <rev>~n
+ multiple git commits with:
+ <rev1>..<rev2>
+ <rev1>...<rev2>
+ <rev>-<count>
+ git merges are ignored
+ -f, --file treat FILE as regular source file
+ --subjective, --strict enable more subjective tests
+ --list-types list the possible message types
+ --types TYPE(,TYPE2...) show only these comma separated message types
+ --ignore TYPE(,TYPE2...) ignore various comma separated message types
+ --show-types show the specific message type in the output
+ --max-line-length=n set the maximum line length, if exceeded, warn
+ --min-conf-desc-length=n set the min description length, if shorter, warn
+ --root=PATH PATH to the kernel tree root
+ --no-summary suppress the per-file summary
+ --mailback only produce a report in case of warnings/errors
+ --summary-file include the filename in summary
+ --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of
+ 'values', 'possible', 'type', and 'attr' (default
+ is all off)
+ --test-only=WORD report only warnings/errors containing WORD
+ literally
+ --fix EXPERIMENTAL - may create horrible results
+ If correctable single-line errors exist, create
+ "<inputfile>.EXPERIMENTAL-checkpatch-fixes"
+ with potential errors corrected to the preferred
+ checkpatch style
+ --fix-inplace EXPERIMENTAL - may create horrible results
+ Is the same as --fix, but overwrites the input
+ file. It's your fault if there's no backup or git
+ --ignore-perl-version override checking of perl version. expect
+ runtime errors.
+ --codespell Use the codespell dictionary for spelling/typos
+ (default:/usr/share/codespell/dictionary.txt)
+ --codespellfile Use this codespell dictionary
+ --color Use colors when output is STDOUT (default: on)
+ -h, --help, --version display this help and exit
+
+When FILE is - read standard input.
+EOM
+
+ exit($exitcode);
+}
+
+sub uniq {
+ my %seen;
+ return grep { !$seen{$_}++ } @_;
+}
+
+sub list_types {
+ my ($exitcode) = @_;
+
+ my $count = 0;
+
+ local $/ = undef;
+
+ open(my $script, '<', abs_path($P)) or
+ die "$P: Can't read '$P' $!\n";
+
+ my $text = <$script>;
+ close($script);
+
+ my @types = ();
+ for ($text =~ /\b(?:(?:CHK|WARN|ERROR)\s*\(\s*"([^"]+)")/g) {
+ push (@types, $_);
+ }
+ @types = sort(uniq(@types));
+ print("#\tMessage type\n\n");
+ foreach my $type (@types) {
+ print(++$count . "\t" . $type . "\n");
+ }
+
+ exit($exitcode);
+}
+
+my $conf = which_conf($configuration_file);
+if (-f $conf) {
+ my @conf_args;
+ open(my $conffile, '<', "$conf")
+ or warn "$P: Can't find a readable $configuration_file file $!\n";
+
+ while (<$conffile>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+ $line =~ s/\s+/ /g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+
+ my @words = split(" ", $line);
+ foreach my $word (@words) {
+ last if ($word =~ m/^#/);
+ push (@conf_args, $word);
+ }
+ }
+ close($conffile);
+ unshift(@ARGV, @conf_args) if @conf_args;
+}
+
+GetOptions(
+ 'q|quiet+' => \$quiet,
+ 'tree!' => \$tree,
+ 'signoff!' => \$chk_signoff,
+ 'patch!' => \$chk_patch,
+ 'emacs!' => \$emacs,
+ 'terse!' => \$terse,
+ 'showfile!' => \$showfile,
+ 'f|file!' => \$file,
+ 'g|git!' => \$git,
+ 'subjective!' => \$check,
+ 'strict!' => \$check,
+ 'ignore=s' => \@ignore,
+ 'types=s' => \@use,
+ 'show-types!' => \$show_types,
+ 'list-types!' => \$list_types,
+ 'max-line-length=i' => \$max_line_length,
+ 'min-conf-desc-length=i' => \$min_conf_desc_length,
+ 'root=s' => \$root,
+ 'summary!' => \$summary,
+ 'mailback!' => \$mailback,
+ 'summary-file!' => \$summary_file,
+ 'fix!' => \$fix,
+ 'fix-inplace!' => \$fix_inplace,
+ 'ignore-perl-version!' => \$ignore_perl_version,
+ 'debug=s' => \%debug,
+ 'test-only=s' => \$tst_only,
+ 'codespell!' => \$codespell,
+ 'codespellfile=s' => \$codespellfile,
+ 'color!' => \$color,
+ 'h|help' => \$help,
+ 'version' => \$help
+) or help(1);
+
+help(0) if ($help);
+
+list_types(0) if ($list_types);
+
+$fix = 1 if ($fix_inplace);
+$check_orig = $check;
+
+my $exit = 0;
+
+if ($^V && $^V lt $minimum_perl_version) {
+ printf "$P: requires at least perl version %vd\n", $minimum_perl_version;
+ if (!$ignore_perl_version) {
+ exit(1);
+ }
+}
+
+#if no filenames are given, push '-' to read patch from stdin
+if ($#ARGV < 0) {
+ push(@ARGV, '-');
+}
+
+sub hash_save_array_words {
+ my ($hashRef, $arrayRef) = @_;
+
+ my @array = split(/,/, join(',', @$arrayRef));
+ foreach my $word (@array) {
+ $word =~ s/\s*\n?$//g;
+ $word =~ s/^\s*//g;
+ $word =~ s/\s+/ /g;
+ $word =~ tr/[a-z]/[A-Z]/;
+
+ next if ($word =~ m/^\s*#/);
+ next if ($word =~ m/^\s*$/);
+
+ $hashRef->{$word}++;
+ }
+}
+
+sub hash_show_words {
+ my ($hashRef, $prefix) = @_;
+
+ if (keys %$hashRef) {
+ print "\nNOTE: $prefix message types:";
+ foreach my $word (sort keys %$hashRef) {
+ print " $word";
+ }
+ print "\n";
+ }
+}
+
+hash_save_array_words(\%ignore_type, \@ignore);
+hash_save_array_words(\%use_type, \@use);
+
+my $dbg_values = 0;
+my $dbg_possible = 0;
+my $dbg_type = 0;
+my $dbg_attr = 0;
+for my $key (keys %debug) {
+ ## no critic
+ eval "\${dbg_$key} = '$debug{$key}';";
+ die "$@" if ($@);
+}
+
+my $rpt_cleaners = 0;
+
+if ($terse) {
+ $emacs = 1;
+ $quiet++;
+}
+
+if ($tree) {
+ if (defined $root) {
+ if (!top_of_kernel_tree($root)) {
+ die "$P: $root: --root does not point at a valid tree\n";
+ }
+ } else {
+ if (top_of_kernel_tree('.')) {
+ $root = '.';
+ } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ &&
+ top_of_kernel_tree($1)) {
+ $root = $1;
+ }
+ }
+
+ if (!defined $root) {
+ print "Must be run from the top-level dir. of a kernel tree\n";
+ exit(2);
+ }
+}
+
+my $emitted_corrupt = 0;
+
+our $Ident = qr{
+ [A-Za-z_][A-Za-z\d_]*
+ (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)*
+ }x;
+our $Storage = qr{extern|static|asmlinkage};
+our $Sparse = qr{
+ __user|
+ __kernel|
+ __force|
+ __iomem|
+ __must_check|
+ __init_refok|
+ __kprobes|
+ __ref|
+ __rcu|
+ __private
+ }x;
+our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)};
+our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)};
+our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)};
+our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)};
+our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit};
+
+# Notes to $Attribute:
+# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check
+our $Attribute = qr{
+ const|
+ __percpu|
+ __nocast|
+ __safe|
+ __bitwise__|
+ __packed__|
+ __packed2__|
+ __naked|
+ __maybe_unused|
+ __always_unused|
+ __noreturn|
+ __used|
+ __cold|
+ __pure|
+ __noclone|
+ __deprecated|
+ __read_mostly|
+ __kprobes|
+ $InitAttribute|
+ ____cacheline_aligned|
+ ____cacheline_aligned_in_smp|
+ ____cacheline_internodealigned_in_smp|
+ __weak
+ }x;
+our $Modifier;
+our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__};
+our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]};
+our $Lval = qr{$Ident(?:$Member)*};
+
+our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u};
+our $Binary = qr{(?i)0b[01]+$Int_type?};
+our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?};
+our $Int = qr{[0-9]+$Int_type?};
+our $Octal = qr{0[0-7]+$Int_type?};
+our $String = qr{"[X\t]*"};
+our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?};
+our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?};
+our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?};
+our $Float = qr{$Float_hex|$Float_dec|$Float_int};
+our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int};
+our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=};
+our $Compare = qr{<=|>=|==|!=|<|(?<!-)>};
+our $Arithmetic = qr{\+|-|\*|\/|%};
+our $Operators = qr{
+ <=|>=|==|!=|
+ =>|->|<<|>>|<|>|!|~|
+ &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic
+ }x;
+
+our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x;
+
+our $BasicType;
+our $NonptrType;
+our $NonptrTypeMisordered;
+our $NonptrTypeWithAttr;
+our $Type;
+our $TypeMisordered;
+our $Declare;
+our $DeclareMisordered;
+
+our $NON_ASCII_UTF8 = qr{
+ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
+ | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
+ | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
+ | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
+ | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
+ | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
+ | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
+}x;
+
+our $UTF8 = qr{
+ [\x09\x0A\x0D\x20-\x7E] # ASCII
+ | $NON_ASCII_UTF8
+}x;
+
+our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t};
+our $typeOtherOSTypedefs = qr{(?x:
+ u_(?:char|short|int|long) | # bsd
+ u(?:nchar|short|int|long) # sysv
+)};
+our $typeKernelTypedefs = qr{(?x:
+ (?:__)?(?:u|s|be|le)(?:8|16|32|64)|
+ atomic_t
+)};
+our $typeTypedefs = qr{(?x:
+ $typeC99Typedefs\b|
+ $typeOtherOSTypedefs\b|
+ $typeKernelTypedefs\b
+)};
+
+our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b};
+
+our $logFunctions = qr{(?x:
+ printk(?:_ratelimited|_once|)|
+ (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)|
+ WARN(?:_RATELIMIT|_ONCE|)|
+ panic|
+ MODULE_[A-Z_]+|
+ seq_vprintf|seq_printf|seq_puts
+)};
+
+our $signature_tags = qr{(?xi:
+ Signed-off-by:|
+ Acked-by:|
+ Tested-by:|
+ Reviewed-by:|
+ Reported-by:|
+ Suggested-by:|
+ To:|
+ Cc:
+)};
+
+our @typeListMisordered = (
+ qr{char\s+(?:un)?signed},
+ qr{int\s+(?:(?:un)?signed\s+)?short\s},
+ qr{int\s+short(?:\s+(?:un)?signed)},
+ qr{short\s+int(?:\s+(?:un)?signed)},
+ qr{(?:un)?signed\s+int\s+short},
+ qr{short\s+(?:un)?signed},
+ qr{long\s+int\s+(?:un)?signed},
+ qr{int\s+long\s+(?:un)?signed},
+ qr{long\s+(?:un)?signed\s+int},
+ qr{int\s+(?:un)?signed\s+long},
+ qr{int\s+(?:un)?signed},
+ qr{int\s+long\s+long\s+(?:un)?signed},
+ qr{long\s+long\s+int\s+(?:un)?signed},
+ qr{long\s+long\s+(?:un)?signed\s+int},
+ qr{long\s+long\s+(?:un)?signed},
+ qr{long\s+(?:un)?signed},
+);
+
+our @typeList = (
+ qr{void},
+ qr{(?:(?:un)?signed\s+)?char},
+ qr{(?:(?:un)?signed\s+)?short\s+int},
+ qr{(?:(?:un)?signed\s+)?short},
+ qr{(?:(?:un)?signed\s+)?int},
+ qr{(?:(?:un)?signed\s+)?long\s+int},
+ qr{(?:(?:un)?signed\s+)?long\s+long\s+int},
+ qr{(?:(?:un)?signed\s+)?long\s+long},
+ qr{(?:(?:un)?signed\s+)?long},
+ qr{(?:un)?signed},
+ qr{float},
+ qr{double},
+ qr{bool},
+ qr{struct\s+$Ident},
+ qr{union\s+$Ident},
+ qr{enum\s+$Ident},
+ qr{${Ident}_t},
+ qr{${Ident}_handler},
+ qr{${Ident}_handler_fn},
+ @typeListMisordered,
+);
+
+our $C90_int_types = qr{(?x:
+ long\s+long\s+int\s+(?:un)?signed|
+ long\s+long\s+(?:un)?signed\s+int|
+ long\s+long\s+(?:un)?signed|
+ (?:(?:un)?signed\s+)?long\s+long\s+int|
+ (?:(?:un)?signed\s+)?long\s+long|
+ int\s+long\s+long\s+(?:un)?signed|
+ int\s+(?:(?:un)?signed\s+)?long\s+long|
+
+ long\s+int\s+(?:un)?signed|
+ long\s+(?:un)?signed\s+int|
+ long\s+(?:un)?signed|
+ (?:(?:un)?signed\s+)?long\s+int|
+ (?:(?:un)?signed\s+)?long|
+ int\s+long\s+(?:un)?signed|
+ int\s+(?:(?:un)?signed\s+)?long|
+
+ int\s+(?:un)?signed|
+ (?:(?:un)?signed\s+)?int
+)};
+
+our @typeListFile = ();
+our @typeListWithAttr = (
+ @typeList,
+ qr{struct\s+$InitAttribute\s+$Ident},
+ qr{union\s+$InitAttribute\s+$Ident},
+);
+
+our @modifierList = (
+ qr{fastcall},
+);
+our @modifierListFile = ();
+
+our @mode_permission_funcs = (
+ ["module_param", 3],
+ ["module_param_(?:array|named|string)", 4],
+ ["module_param_array_named", 5],
+ ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2],
+ ["proc_create(?:_data|)", 2],
+ ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2],
+ ["IIO_DEV_ATTR_[A-Z_]+", 1],
+ ["SENSOR_(?:DEVICE_|)ATTR_2", 2],
+ ["SENSOR_TEMPLATE(?:_2|)", 3],
+ ["__ATTR", 2],
+);
+
+#Create a search pattern for all these functions to speed up a loop below
+our $mode_perms_search = "";
+foreach my $entry (@mode_permission_funcs) {
+ $mode_perms_search .= '|' if ($mode_perms_search ne "");
+ $mode_perms_search .= $entry->[0];
+}
+
+our $mode_perms_world_writable = qr{
+ S_IWUGO |
+ S_IWOTH |
+ S_IRWXUGO |
+ S_IALLUGO |
+ 0[0-7][0-7][2367]
+}x;
+
+our %mode_permission_string_types = (
+ "S_IRWXU" => 0700,
+ "S_IRUSR" => 0400,
+ "S_IWUSR" => 0200,
+ "S_IXUSR" => 0100,
+ "S_IRWXG" => 0070,
+ "S_IRGRP" => 0040,
+ "S_IWGRP" => 0020,
+ "S_IXGRP" => 0010,
+ "S_IRWXO" => 0007,
+ "S_IROTH" => 0004,
+ "S_IWOTH" => 0002,
+ "S_IXOTH" => 0001,
+ "S_IRWXUGO" => 0777,
+ "S_IRUGO" => 0444,
+ "S_IWUGO" => 0222,
+ "S_IXUGO" => 0111,
+);
+
+#Create a search pattern for all these strings to speed up a loop below
+our $mode_perms_string_search = "";
+foreach my $entry (keys %mode_permission_string_types) {
+ $mode_perms_string_search .= '|' if ($mode_perms_string_search ne "");
+ $mode_perms_string_search .= $entry;
+}
+
+our $allowed_asm_includes = qr{(?x:
+ irq|
+ memory|
+ time|
+ reboot
+)};
+# memory.h: ARM has a custom one
+
+# Load common spelling mistakes and build regular expression list.
+my $misspellings;
+my %spelling_fix;
+
+if (open(my $spelling, '<', $spelling_file)) {
+ while (<$spelling>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+
+ my ($suspect, $fix) = split(/\|\|/, $line);
+
+ $spelling_fix{$suspect} = $fix;
+ }
+ close($spelling);
+} else {
+ warn "No typos will be found - file '$spelling_file': $!\n";
+}
+
+if ($codespell) {
+ if (open(my $spelling, '<', $codespellfile)) {
+ while (<$spelling>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+ next if ($line =~ m/, disabled/i);
+
+ $line =~ s/,.*$//;
+
+ my ($suspect, $fix) = split(/->/, $line);
+
+ $spelling_fix{$suspect} = $fix;
+ }
+ close($spelling);
+ } else {
+ warn "No codespell typos will be found - file '$codespellfile': $!\n";
+ }
+}
+
+$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix;
+
+my $const_structs = "";
+if (open(my $conststructs, '<', $conststructsfile)) {
+ while (<$conststructs>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+ if ($line =~ /\s/) {
+ print("$conststructsfile: '$line' invalid - ignored\n");
+ next;
+ }
+
+ $const_structs .= '|' if ($const_structs ne "");
+ $const_structs .= $line;
+ }
+ close($conststructsfile);
+} else {
+ warn "No structs that should be const will be found - file '$conststructsfile': $!\n";
+}
+
+sub build_types {
+ my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)";
+ my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)";
+ my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)";
+ my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)";
+ $Modifier = qr{(?:$Attribute|$Sparse|$mods)};
+ $BasicType = qr{
+ (?:$typeTypedefs\b)|
+ (?:${all}\b)
+ }x;
+ $NonptrType = qr{
+ (?:$Modifier\s+|const\s+)*
+ (?:
+ (?:typeof|__typeof__)\s*\([^\)]*\)|
+ (?:$typeTypedefs\b)|
+ (?:${all}\b)
+ )
+ (?:\s+$Modifier|\s+const)*
+ }x;
+ $NonptrTypeMisordered = qr{
+ (?:$Modifier\s+|const\s+)*
+ (?:
+ (?:${Misordered}\b)
+ )
+ (?:\s+$Modifier|\s+const)*
+ }x;
+ $NonptrTypeWithAttr = qr{
+ (?:$Modifier\s+|const\s+)*
+ (?:
+ (?:typeof|__typeof__)\s*\([^\)]*\)|
+ (?:$typeTypedefs\b)|
+ (?:${allWithAttr}\b)
+ )
+ (?:\s+$Modifier|\s+const)*
+ }x;
+ $Type = qr{
+ $NonptrType
+ (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)?
+ (?:\s+$Inline|\s+$Modifier)*
+ }x;
+ $TypeMisordered = qr{
+ $NonptrTypeMisordered
+ (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)?
+ (?:\s+$Inline|\s+$Modifier)*
+ }x;
+ $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type};
+ $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered};
+}
+build_types();
+
+our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*};
+
+# Using $balanced_parens, $LvalOrFunc, or $FuncArg
+# requires at least perl version v5.10.0
+# Any use must be runtime checked with $^V
+
+our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/;
+our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*};
+our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)};
+
+our $declaration_macros = qr{(?x:
+ (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(|
+ (?:$Storage\s+)?LIST_HEAD\s*\(|
+ (?:$Storage\s+)?${Type}\s+uninitialized_var\s*\(
+)};
+
+sub deparenthesize {
+ my ($string) = @_;
+ return "" if (!defined($string));
+
+ while ($string =~ /^\s*\(.*\)\s*$/) {
+ $string =~ s@^\s*\(\s*@@;
+ $string =~ s@\s*\)\s*$@@;
+ }
+
+ $string =~ s@\s+@ @g;
+
+ return $string;
+}
+
+sub seed_camelcase_file {
+ my ($file) = @_;
+
+ return if (!(-f $file));
+
+ local $/;
+
+ open(my $include_file, '<', "$file")
+ or warn "$P: Can't read '$file' $!\n";
+ my $text = <$include_file>;
+ close($include_file);
+
+ my @lines = split('\n', $text);
+
+ foreach my $line (@lines) {
+ next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/);
+ if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) {
+ $camelcase{$1} = 1;
+ } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) {
+ $camelcase{$1} = 1;
+ } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) {
+ $camelcase{$1} = 1;
+ }
+ }
+}
+
+sub is_maintained_obsolete {
+ my ($filename) = @_;
+
+ return 0 if (!(-e "$root/scripts/get_maintainer.pl"));
+
+ my $status = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`;
+
+ return $status =~ /obsolete/i;
+}
+
+my $camelcase_seeded = 0;
+sub seed_camelcase_includes {
+ return if ($camelcase_seeded);
+
+ my $files;
+ my $camelcase_cache = "";
+ my @include_files = ();
+
+ $camelcase_seeded = 1;
+
+ if (-e ".git") {
+ my $git_last_include_commit = `git log --no-merges --pretty=format:"%h%n" -1 -- include`;
+ chomp $git_last_include_commit;
+ $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit";
+ } else {
+ my $last_mod_date = 0;
+ $files = `find $root/include -name "*.h"`;
+ @include_files = split('\n', $files);
+ foreach my $file (@include_files) {
+ my $date = POSIX::strftime("%Y%m%d%H%M",
+ localtime((stat $file)[9]));
+ $last_mod_date = $date if ($last_mod_date < $date);
+ }
+ $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date";
+ }
+
+ if ($camelcase_cache ne "" && -f $camelcase_cache) {
+ open(my $camelcase_file, '<', "$camelcase_cache")
+ or warn "$P: Can't read '$camelcase_cache' $!\n";
+ while (<$camelcase_file>) {
+ chomp;
+ $camelcase{$_} = 1;
+ }
+ close($camelcase_file);
+
+ return;
+ }
+
+ if (-e ".git") {
+ $files = `git ls-files "include/*.h"`;
+ @include_files = split('\n', $files);
+ }
+
+ foreach my $file (@include_files) {
+ seed_camelcase_file($file);
+ }
+
+ if ($camelcase_cache ne "") {
+ unlink glob ".checkpatch-camelcase.*";
+ open(my $camelcase_file, '>', "$camelcase_cache")
+ or warn "$P: Can't write '$camelcase_cache' $!\n";
+ foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) {
+ print $camelcase_file ("$_\n");
+ }
+ close($camelcase_file);
+ }
+}
+
+sub git_commit_info {
+ my ($commit, $id, $desc) = @_;
+
+ return ($id, $desc) if ((which("git") eq "") || !(-e ".git"));
+
+ my $output = `git log --no-color --format='%H %s' -1 $commit 2>&1`;
+ $output =~ s/^\s*//gm;
+ my @lines = split("\n", $output);
+
+ return ($id, $desc) if ($#lines < 0);
+
+ if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous\./) {
+# Maybe one day convert this block of bash into something that returns
+# all matching commit ids, but it's very slow...
+#
+# echo "checking commits $1..."
+# git rev-list --remotes | grep -i "^$1" |
+# while read line ; do
+# git log --format='%H %s' -1 $line |
+# echo "commit $(cut -c 1-12,41-)"
+# done
+ } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./) {
+ } else {
+ $id = substr($lines[0], 0, 12);
+ $desc = substr($lines[0], 41);
+ }
+
+ return ($id, $desc);
+}
+
+$chk_signoff = 0 if ($file);
+
+my @rawlines = ();
+my @lines = ();
+my @fixed = ();
+my @fixed_inserted = ();
+my @fixed_deleted = ();
+my $fixlinenr = -1;
+
+# If input is git commits, extract all commits from the commit expressions.
+# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'.
+die "$P: No git repository found\n" if ($git && !-e ".git");
+
+if ($git) {
+ my @commits = ();
+ foreach my $commit_expr (@ARGV) {
+ my $git_range;
+ if ($commit_expr =~ m/^(.*)-(\d+)$/) {
+ $git_range = "-$2 $1";
+ } elsif ($commit_expr =~ m/\.\./) {
+ $git_range = "$commit_expr";
+ } else {
+ $git_range = "-1 $commit_expr";
+ }
+ my $lines = `git log --no-color --no-merges --pretty=format:'%H %s' $git_range`;
+ foreach my $line (split(/\n/, $lines)) {
+ $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/;
+ next if (!defined($1) || !defined($2));
+ my $sha1 = $1;
+ my $subject = $2;
+ unshift(@commits, $sha1);
+ $git_commits{$sha1} = $subject;
+ }
+ }
+ die "$P: no git commits after extraction!\n" if (@commits == 0);
+ @ARGV = @commits;
+}
+
+my $vname;
+for my $filename (@ARGV) {
+ my $FILE;
+ if ($git) {
+ open($FILE, '-|', "git format-patch -M --stdout -1 $filename") ||
+ die "$P: $filename: git format-patch failed - $!\n";
+ } elsif ($file) {
+ open($FILE, '-|', "diff -u /dev/null $filename") ||
+ die "$P: $filename: diff failed - $!\n";
+ } elsif ($filename eq '-') {
+ open($FILE, '<&STDIN');
+ } else {
+ open($FILE, '<', "$filename") ||
+ die "$P: $filename: open failed - $!\n";
+ }
+ if ($filename eq '-') {
+ $vname = 'Your patch';
+ } elsif ($git) {
+ $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")';
+ } else {
+ $vname = $filename;
+ }
+ while (<$FILE>) {
+ chomp;
+ push(@rawlines, $_);
+ }
+ close($FILE);
+
+ if ($#ARGV > 0 && $quiet == 0) {
+ print '-' x length($vname) . "\n";
+ print "$vname\n";
+ print '-' x length($vname) . "\n";
+ }
+
+ if (!process($filename)) {
+# $exit = 1;
+ }
+ @rawlines = ();
+ @lines = ();
+ @fixed = ();
+ @fixed_inserted = ();
+ @fixed_deleted = ();
+ $fixlinenr = -1;
+ @modifierListFile = ();
+ @typeListFile = ();
+ build_types();
+}
+
+if (!$quiet) {
+ hash_show_words(\%use_type, "Used");
+ hash_show_words(\%ignore_type, "Ignored");
+
+ if ($^V lt 5.10.0) {
+ print << "EOM"
+
+NOTE: perl $^V is not modern enough to detect all possible issues.
+ An upgrade to at least perl v5.10.0 is suggested.
+EOM
+ }
+ if ($exit) {
+ print << "EOM"
+
+NOTE: If any of the errors are false positives, please report
+ them to the maintainer, see CHECKPATCH in MAINTAINERS.
+EOM
+ }
+}
+
+exit($exit);
+
+sub top_of_kernel_tree {
+ my ($root) = @_;
+
+ my @tree_check = (
+ "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile",
+ "README", "Documentation", "arch", "include", "drivers",
+ "fs", "init", "ipc", "kernel", "lib", "scripts",
+ );
+
+ foreach my $check (@tree_check) {
+ if (! -e $root . '/' . $check) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+sub parse_email {
+ my ($formatted_email) = @_;
+
+ my $name = "";
+ my $address = "";
+ my $comment = "";
+
+ if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) {
+ $name = $1;
+ $address = $2;
+ $comment = $3 if defined $3;
+ } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) {
+ $address = $1;
+ $comment = $2 if defined $2;
+ } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) {
+ $address = $1;
+ $comment = $2 if defined $2;
+ $formatted_email =~ s/$address.*$//;
+ $name = $formatted_email;
+ $name = trim($name);
+ $name =~ s/^\"|\"$//g;
+ # If there's a name left after stripping spaces and
+ # leading quotes, and the address doesn't have both
+ # leading and trailing angle brackets, the address
+ # is invalid. ie:
+ # "joe smith joe@smith.com" bad
+ # "joe smith <joe@smith.com" bad
+ if ($name ne "" && $address !~ /^<[^>]+>$/) {
+ $name = "";
+ $address = "";
+ $comment = "";
+ }
+ }
+
+ $name = trim($name);
+ $name =~ s/^\"|\"$//g;
+ $address = trim($address);
+ $address =~ s/^\<|\>$//g;
+
+ if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
+ $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ return ($name, $address, $comment);
+}
+
+sub format_email {
+ my ($name, $address) = @_;
+
+ my $formatted_email;
+
+ $name = trim($name);
+ $name =~ s/^\"|\"$//g;
+ $address = trim($address);
+
+ if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
+ $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ if ("$name" eq "") {
+ $formatted_email = "$address";
+ } else {
+ $formatted_email = "$name <$address>";
+ }
+
+ return $formatted_email;
+}
+
+sub which {
+ my ($bin) = @_;
+
+ foreach my $path (split(/:/, $ENV{PATH})) {
+ if (-e "$path/$bin") {
+ return "$path/$bin";
+ }
+ }
+
+ return "";
+}
+
+sub which_conf {
+ my ($conf) = @_;
+
+ foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
+ if (-e "$path/$conf") {
+ return "$path/$conf";
+ }
+ }
+
+ return "";
+}
+
+sub expand_tabs {
+ my ($str) = @_;
+
+ my $res = '';
+ my $n = 0;
+ for my $c (split(//, $str)) {
+ if ($c eq "\t") {
+ $res .= ' ';
+ $n++;
+ for (; ($n % 8) != 0; $n++) {
+ $res .= ' ';
+ }
+ next;
+ }
+ $res .= $c;
+ $n++;
+ }
+
+ return $res;
+}
+sub copy_spacing {
+ (my $res = shift) =~ tr/\t/ /c;
+ return $res;
+}
+
+sub line_stats {
+ my ($line) = @_;
+
+ # Drop the diff line leader and expand tabs
+ $line =~ s/^.//;
+ $line = expand_tabs($line);
+
+ # Pick the indent from the front of the line.
+ my ($white) = ($line =~ /^(\s*)/);
+
+ return (length($line), length($white));
+}
+
+my $sanitise_quote = '';
+
+sub sanitise_line_reset {
+ my ($in_comment) = @_;
+
+ if ($in_comment) {
+ $sanitise_quote = '*/';
+ } else {
+ $sanitise_quote = '';
+ }
+}
+sub sanitise_line {
+ my ($line) = @_;
+
+ my $res = '';
+ my $l = '';
+
+ my $qlen = 0;
+ my $off = 0;
+ my $c;
+
+ # Always copy over the diff marker.
+ $res = substr($line, 0, 1);
+
+ for ($off = 1; $off < length($line); $off++) {
+ $c = substr($line, $off, 1);
+
+ # Comments we are wacking completly including the begin
+ # and end, all to $;.
+ if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') {
+ $sanitise_quote = '*/';
+
+ substr($res, $off, 2, "$;$;");
+ $off++;
+ next;
+ }
+ if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') {
+ $sanitise_quote = '';
+ substr($res, $off, 2, "$;$;");
+ $off++;
+ next;
+ }
+ if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') {
+ $sanitise_quote = '//';
+
+ substr($res, $off, 2, $sanitise_quote);
+ $off++;
+ next;
+ }
+
+ # A \ in a string means ignore the next character.
+ if (($sanitise_quote eq "'" || $sanitise_quote eq '"') &&
+ $c eq "\\") {
+ substr($res, $off, 2, 'XX');
+ $off++;
+ next;
+ }
+ # Regular quotes.
+ if ($c eq "'" || $c eq '"') {
+ if ($sanitise_quote eq '') {
+ $sanitise_quote = $c;
+
+ substr($res, $off, 1, $c);
+ next;
+ } elsif ($sanitise_quote eq $c) {
+ $sanitise_quote = '';
+ }
+ }
+
+ #print "c<$c> SQ<$sanitise_quote>\n";
+ if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") {
+ substr($res, $off, 1, $;);
+ } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") {
+ substr($res, $off, 1, $;);
+ } elsif ($off != 0 && $sanitise_quote && $c ne "\t") {
+ substr($res, $off, 1, 'X');
+ } else {
+ substr($res, $off, 1, $c);
+ }
+ }
+
+ if ($sanitise_quote eq '//') {
+ $sanitise_quote = '';
+ }
+
+ # The pathname on a #include may be surrounded by '<' and '>'.
+ if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) {
+ my $clean = 'X' x length($1);
+ $res =~ s@\<.*\>@<$clean>@;
+
+ # The whole of a #error is a string.
+ } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) {
+ my $clean = 'X' x length($1);
+ $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@;
+ }
+
+ if ($allow_c99_comments && $res =~ m@(//.*$)@) {
+ my $match = $1;
+ $res =~ s/\Q$match\E/"$;" x length($match)/e;
+ }
+
+ return $res;
+}
+
+sub get_quoted_string {
+ my ($line, $rawline) = @_;
+
+ return "" if ($line !~ m/($String)/g);
+ return substr($rawline, $-[0], $+[0] - $-[0]);
+}
+
+sub ctx_statement_block {
+ my ($linenr, $remain, $off) = @_;
+ my $line = $linenr - 1;
+ my $blk = '';
+ my $soff = $off;
+ my $coff = $off - 1;
+ my $coff_set = 0;
+
+ my $loff = 0;
+
+ my $type = '';
+ my $level = 0;
+ my @stack = ();
+ my $p;
+ my $c;
+ my $len = 0;
+
+ my $remainder;
+ while (1) {
+ @stack = (['', 0]) if ($#stack == -1);
+
+ #warn "CSB: blk<$blk> remain<$remain>\n";
+ # If we are about to drop off the end, pull in more
+ # context.
+ if ($off >= $len) {
+ for (; $remain > 0; $line++) {
+ last if (!defined $lines[$line]);
+ next if ($lines[$line] =~ /^-/);
+ $remain--;
+ $loff = $len;
+ $blk .= $lines[$line] . "\n";
+ $len = length($blk);
+ $line++;
+ last;
+ }
+ # Bail if there is no further context.
+ #warn "CSB: blk<$blk> off<$off> len<$len>\n";
+ if ($off >= $len) {
+ last;
+ }
+ if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) {
+ $level++;
+ $type = '#';
+ }
+ }
+ $p = $c;
+ $c = substr($blk, $off, 1);
+ $remainder = substr($blk, $off);
+
+ #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n";
+
+ # Handle nested #if/#else.
+ if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) {
+ push(@stack, [ $type, $level ]);
+ } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) {
+ ($type, $level) = @{$stack[$#stack - 1]};
+ } elsif ($remainder =~ /^#\s*endif\b/) {
+ ($type, $level) = @{pop(@stack)};
+ }
+
+ # Statement ends at the ';' or a close '}' at the
+ # outermost level.
+ if ($level == 0 && $c eq ';') {
+ last;
+ }
+
+ # An else is really a conditional as long as its not else if
+ if ($level == 0 && $coff_set == 0 &&
+ (!defined($p) || $p =~ /(?:\s|\}|\+)/) &&
+ $remainder =~ /^(else)(?:\s|{)/ &&
+ $remainder !~ /^else\s+if\b/) {
+ $coff = $off + length($1) - 1;
+ $coff_set = 1;
+ #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n";
+ #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n";
+ }
+
+ if (($type eq '' || $type eq '(') && $c eq '(') {
+ $level++;
+ $type = '(';
+ }
+ if ($type eq '(' && $c eq ')') {
+ $level--;
+ $type = ($level != 0)? '(' : '';
+
+ if ($level == 0 && $coff < $soff) {
+ $coff = $off;
+ $coff_set = 1;
+ #warn "CSB: mark coff<$coff>\n";
+ }
+ }
+ if (($type eq '' || $type eq '{') && $c eq '{') {
+ $level++;
+ $type = '{';
+ }
+ if ($type eq '{' && $c eq '}') {
+ $level--;
+ $type = ($level != 0)? '{' : '';
+
+ if ($level == 0) {
+ if (substr($blk, $off + 1, 1) eq ';') {
+ $off++;
+ }
+ last;
+ }
+ }
+ # Preprocessor commands end at the newline unless escaped.
+ if ($type eq '#' && $c eq "\n" && $p ne "\\") {
+ $level--;
+ $type = '';
+ $off++;
+ last;
+ }
+ $off++;
+ }
+ # We are truly at the end, so shuffle to the next line.
+ if ($off == $len) {
+ $loff = $len + 1;
+ $line++;
+ $remain--;
+ }
+
+ my $statement = substr($blk, $soff, $off - $soff + 1);
+ my $condition = substr($blk, $soff, $coff - $soff + 1);
+
+ #warn "STATEMENT<$statement>\n";
+ #warn "CONDITION<$condition>\n";
+
+ #print "coff<$coff> soff<$off> loff<$loff>\n";
+
+ return ($statement, $condition,
+ $line, $remain + 1, $off - $loff + 1, $level);
+}
+
+sub statement_lines {
+ my ($stmt) = @_;
+
+ # Strip the diff line prefixes and rip blank lines at start and end.
+ $stmt =~ s/(^|\n)./$1/g;
+ $stmt =~ s/^\s*//;
+ $stmt =~ s/\s*$//;
+
+ my @stmt_lines = ($stmt =~ /\n/g);
+
+ return $#stmt_lines + 2;
+}
+
+sub statement_rawlines {
+ my ($stmt) = @_;
+
+ my @stmt_lines = ($stmt =~ /\n/g);
+
+ return $#stmt_lines + 2;
+}
+
+sub statement_block_size {
+ my ($stmt) = @_;
+
+ $stmt =~ s/(^|\n)./$1/g;
+ $stmt =~ s/^\s*{//;
+ $stmt =~ s/}\s*$//;
+ $stmt =~ s/^\s*//;
+ $stmt =~ s/\s*$//;
+
+ my @stmt_lines = ($stmt =~ /\n/g);
+ my @stmt_statements = ($stmt =~ /;/g);
+
+ my $stmt_lines = $#stmt_lines + 2;
+ my $stmt_statements = $#stmt_statements + 1;
+
+ if ($stmt_lines > $stmt_statements) {
+ return $stmt_lines;
+ } else {
+ return $stmt_statements;
+ }
+}
+
+sub ctx_statement_full {
+ my ($linenr, $remain, $off) = @_;
+ my ($statement, $condition, $level);
+
+ my (@chunks);
+
+ # Grab the first conditional/block pair.
+ ($statement, $condition, $linenr, $remain, $off, $level) =
+ ctx_statement_block($linenr, $remain, $off);
+ #print "F: c<$condition> s<$statement> remain<$remain>\n";
+ push(@chunks, [ $condition, $statement ]);
+ if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) {
+ return ($level, $linenr, @chunks);
+ }
+
+ # Pull in the following conditional/block pairs and see if they
+ # could continue the statement.
+ for (;;) {
+ ($statement, $condition, $linenr, $remain, $off, $level) =
+ ctx_statement_block($linenr, $remain, $off);
+ #print "C: c<$condition> s<$statement> remain<$remain>\n";
+ last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s));
+ #print "C: push\n";
+ push(@chunks, [ $condition, $statement ]);
+ }
+
+ return ($level, $linenr, @chunks);
+}
+
+sub ctx_block_get {
+ my ($linenr, $remain, $outer, $open, $close, $off) = @_;
+ my $line;
+ my $start = $linenr - 1;
+ my $blk = '';
+ my @o;
+ my @c;
+ my @res = ();
+
+ my $level = 0;
+ my @stack = ($level);
+ for ($line = $start; $remain > 0; $line++) {
+ next if ($rawlines[$line] =~ /^-/);
+ $remain--;
+
+ $blk .= $rawlines[$line];
+
+ # Handle nested #if/#else.
+ if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) {
+ push(@stack, $level);
+ } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) {
+ $level = $stack[$#stack - 1];
+ } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) {
+ $level = pop(@stack);
+ }
+
+ foreach my $c (split(//, $lines[$line])) {
+ ##print "C<$c>L<$level><$open$close>O<$off>\n";
+ if ($off > 0) {
+ $off--;
+ next;
+ }
+
+ if ($c eq $close && $level > 0) {
+ $level--;
+ last if ($level == 0);
+ } elsif ($c eq $open) {
+ $level++;
+ }
+ }
+
+ if (!$outer || $level <= 1) {
+ push(@res, $rawlines[$line]);
+ }
+
+ last if ($level == 0);
+ }
+
+ return ($level, @res);
+}
+sub ctx_block_outer {
+ my ($linenr, $remain) = @_;
+
+ my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0);
+ return @r;
+}
+sub ctx_block {
+ my ($linenr, $remain) = @_;
+
+ my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0);
+ return @r;
+}
+sub ctx_statement {
+ my ($linenr, $remain, $off) = @_;
+
+ my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off);
+ return @r;
+}
+sub ctx_block_level {
+ my ($linenr, $remain) = @_;
+
+ return ctx_block_get($linenr, $remain, 0, '{', '}', 0);
+}
+sub ctx_statement_level {
+ my ($linenr, $remain, $off) = @_;
+
+ return ctx_block_get($linenr, $remain, 0, '(', ')', $off);
+}
+
+sub ctx_locate_comment {
+ my ($first_line, $end_line) = @_;
+
+ # Catch a comment on the end of the line itself.
+ my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@);
+ return $current_comment if (defined $current_comment);
+
+ # Look through the context and try and figure out if there is a
+ # comment.
+ my $in_comment = 0;
+ $current_comment = '';
+ for (my $linenr = $first_line; $linenr < $end_line; $linenr++) {
+ my $line = $rawlines[$linenr - 1];
+ #warn " $line\n";
+ if ($linenr == $first_line and $line =~ m@^.\s*\*@) {
+ $in_comment = 1;
+ }
+ if ($line =~ m@/\*@) {
+ $in_comment = 1;
+ }
+ if (!$in_comment && $current_comment ne '') {
+ $current_comment = '';
+ }
+ $current_comment .= $line . "\n" if ($in_comment);
+ if ($line =~ m@\*/@) {
+ $in_comment = 0;
+ }
+ }
+
+ chomp($current_comment);
+ return($current_comment);
+}
+sub ctx_has_comment {
+ my ($first_line, $end_line) = @_;
+ my $cmt = ctx_locate_comment($first_line, $end_line);
+
+ ##print "LINE: $rawlines[$end_line - 1 ]\n";
+ ##print "CMMT: $cmt\n";
+
+ return ($cmt ne '');
+}
+
+sub raw_line {
+ my ($linenr, $cnt) = @_;
+
+ my $offset = $linenr - 1;
+ $cnt++;
+
+ my $line;
+ while ($cnt) {
+ $line = $rawlines[$offset++];
+ next if (defined($line) && $line =~ /^-/);
+ $cnt--;
+ }
+
+ return $line;
+}
+
+sub cat_vet {
+ my ($vet) = @_;
+ my ($res, $coded);
+
+ $res = '';
+ while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) {
+ $res .= $1;
+ if ($2 ne '') {
+ $coded = sprintf("^%c", unpack('C', $2) + 64);
+ $res .= $coded;
+ }
+ }
+ $res =~ s/$/\$/;
+
+ return $res;
+}
+
+my $av_preprocessor = 0;
+my $av_pending;
+my @av_paren_type;
+my $av_pend_colon;
+
+sub annotate_reset {
+ $av_preprocessor = 0;
+ $av_pending = '_';
+ @av_paren_type = ('E');
+ $av_pend_colon = 'O';
+}
+
+sub annotate_values {
+ my ($stream, $type) = @_;
+
+ my $res;
+ my $var = '_' x length($stream);
+ my $cur = $stream;
+
+ print "$stream\n" if ($dbg_values > 1);
+
+ while (length($cur)) {
+ @av_paren_type = ('E') if ($#av_paren_type < 0);
+ print " <" . join('', @av_paren_type) .
+ "> <$type> <$av_pending>" if ($dbg_values > 1);
+ if ($cur =~ /^(\s+)/o) {
+ print "WS($1)\n" if ($dbg_values > 1);
+ if ($1 =~ /\n/ && $av_preprocessor) {
+ $type = pop(@av_paren_type);
+ $av_preprocessor = 0;
+ }
+
+ } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') {
+ print "CAST($1)\n" if ($dbg_values > 1);
+ push(@av_paren_type, $type);
+ $type = 'c';
+
+ } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) {
+ print "DECLARE($1)\n" if ($dbg_values > 1);
+ $type = 'T';
+
+ } elsif ($cur =~ /^($Modifier)\s*/) {
+ print "MODIFIER($1)\n" if ($dbg_values > 1);
+ $type = 'T';
+
+ } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) {
+ print "DEFINE($1,$2)\n" if ($dbg_values > 1);
+ $av_preprocessor = 1;
+ push(@av_paren_type, $type);
+ if ($2 ne '') {
+ $av_pending = 'N';
+ }
+ $type = 'E';
+
+ } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) {
+ print "UNDEF($1)\n" if ($dbg_values > 1);
+ $av_preprocessor = 1;
+ push(@av_paren_type, $type);
+
+ } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) {
+ print "PRE_START($1)\n" if ($dbg_values > 1);
+ $av_preprocessor = 1;
+
+ push(@av_paren_type, $type);
+ push(@av_paren_type, $type);
+ $type = 'E';
+
+ } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) {
+ print "PRE_RESTART($1)\n" if ($dbg_values > 1);
+ $av_preprocessor = 1;
+
+ push(@av_paren_type, $av_paren_type[$#av_paren_type]);
+
+ $type = 'E';
+
+ } elsif ($cur =~ /^(\#\s*(?:endif))/o) {
+ print "PRE_END($1)\n" if ($dbg_values > 1);
+
+ $av_preprocessor = 1;
+
+ # Assume all arms of the conditional end as this
+ # one does, and continue as if the #endif was not here.
+ pop(@av_paren_type);
+ push(@av_paren_type, $type);
+ $type = 'E';
+
+ } elsif ($cur =~ /^(\\\n)/o) {
+ print "PRECONT($1)\n" if ($dbg_values > 1);
+
+ } elsif ($cur =~ /^(__attribute__)\s*\(?/o) {
+ print "ATTR($1)\n" if ($dbg_values > 1);
+ $av_pending = $type;
+ $type = 'N';
+
+ } elsif ($cur =~ /^(sizeof)\s*(\()?/o) {
+ print "SIZEOF($1)\n" if ($dbg_values > 1);
+ if (defined $2) {
+ $av_pending = 'V';
+ }
+ $type = 'N';
+
+ } elsif ($cur =~ /^(if|while|for)\b/o) {
+ print "COND($1)\n" if ($dbg_values > 1);
+ $av_pending = 'E';
+ $type = 'N';
+
+ } elsif ($cur =~/^(case)/o) {
+ print "CASE($1)\n" if ($dbg_values > 1);
+ $av_pend_colon = 'C';
+ $type = 'N';
+
+ } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) {
+ print "KEYWORD($1)\n" if ($dbg_values > 1);
+ $type = 'N';
+
+ } elsif ($cur =~ /^(\()/o) {
+ print "PAREN('$1')\n" if ($dbg_values > 1);
+ push(@av_paren_type, $av_pending);
+ $av_pending = '_';
+ $type = 'N';
+
+ } elsif ($cur =~ /^(\))/o) {
+ my $new_type = pop(@av_paren_type);
+ if ($new_type ne '_') {
+ $type = $new_type;
+ print "PAREN('$1') -> $type\n"
+ if ($dbg_values > 1);
+ } else {
+ print "PAREN('$1')\n" if ($dbg_values > 1);
+ }
+
+ } elsif ($cur =~ /^($Ident)\s*\(/o) {
+ print "FUNC($1)\n" if ($dbg_values > 1);
+ $type = 'V';
+ $av_pending = 'V';
+
+ } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) {
+ if (defined $2 && $type eq 'C' || $type eq 'T') {
+ $av_pend_colon = 'B';
+ } elsif ($type eq 'E') {
+ $av_pend_colon = 'L';
+ }
+ print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1);
+ $type = 'V';
+
+ } elsif ($cur =~ /^($Ident|$Constant)/o) {
+ print "IDENT($1)\n" if ($dbg_values > 1);
+ $type = 'V';
+
+ } elsif ($cur =~ /^($Assignment)/o) {
+ print "ASSIGN($1)\n" if ($dbg_values > 1);
+ $type = 'N';
+
+ } elsif ($cur =~/^(;|{|})/) {
+ print "END($1)\n" if ($dbg_values > 1);
+ $type = 'E';
+ $av_pend_colon = 'O';
+
+ } elsif ($cur =~/^(,)/) {
+ print "COMMA($1)\n" if ($dbg_values > 1);
+ $type = 'C';
+
+ } elsif ($cur =~ /^(\?)/o) {
+ print "QUESTION($1)\n" if ($dbg_values > 1);
+ $type = 'N';
+
+ } elsif ($cur =~ /^(:)/o) {
+ print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1);
+
+ substr($var, length($res), 1, $av_pend_colon);
+ if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') {
+ $type = 'E';
+ } else {
+ $type = 'N';
+ }
+ $av_pend_colon = 'O';
+
+ } elsif ($cur =~ /^(\[)/o) {
+ print "CLOSE($1)\n" if ($dbg_values > 1);
+ $type = 'N';
+
+ } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) {
+ my $variant;
+
+ print "OPV($1)\n" if ($dbg_values > 1);
+ if ($type eq 'V') {
+ $variant = 'B';
+ } else {
+ $variant = 'U';
+ }
+
+ substr($var, length($res), 1, $variant);
+ $type = 'N';
+
+ } elsif ($cur =~ /^($Operators)/o) {
+ print "OP($1)\n" if ($dbg_values > 1);
+ if ($1 ne '++' && $1 ne '--') {
+ $type = 'N';
+ }
+
+ } elsif ($cur =~ /(^.)/o) {
+ print "C($1)\n" if ($dbg_values > 1);
+ }
+ if (defined $1) {
+ $cur = substr($cur, length($1));
+ $res .= $type x length($1);
+ }
+ }
+
+ return ($res, $var);
+}
+
+sub possible {
+ my ($possible, $line) = @_;
+ my $notPermitted = qr{(?:
+ ^(?:
+ $Modifier|
+ $Storage|
+ $Type|
+ DEFINE_\S+
+ )$|
+ ^(?:
+ goto|
+ return|
+ case|
+ else|
+ asm|__asm__|
+ do|
+ \#|
+ \#\#|
+ )(?:\s|$)|
+ ^(?:typedef|struct|enum)\b
+ )}x;
+ warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2);
+ if ($possible !~ $notPermitted) {
+ # Check for modifiers.
+ $possible =~ s/\s*$Storage\s*//g;
+ $possible =~ s/\s*$Sparse\s*//g;
+ if ($possible =~ /^\s*$/) {
+
+ } elsif ($possible =~ /\s/) {
+ $possible =~ s/\s*$Type\s*//g;
+ for my $modifier (split(' ', $possible)) {
+ if ($modifier !~ $notPermitted) {
+ warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible);
+ push(@modifierListFile, $modifier);
+ }
+ }
+
+ } else {
+ warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible);
+ push(@typeListFile, $possible);
+ }
+ build_types();
+ } else {
+ warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1);
+ }
+}
+
+my $prefix = '';
+
+sub show_type {
+ my ($type) = @_;
+
+ return defined $use_type{$type} if (scalar keys %use_type > 0);
+
+ return !defined $ignore_type{$type};
+}
+
+sub report {
+ my ($level, $type, $msg) = @_;
+
+ if (!show_type($type) ||
+ (defined $tst_only && $msg !~ /\Q$tst_only\E/)) {
+ return 0;
+ }
+ my $output = '';
+ if (-t STDOUT && $color) {
+ if ($level eq 'ERROR') {
+ $output .= RED;
+ } elsif ($level eq 'WARNING') {
+ $output .= YELLOW;
+ } else {
+ $output .= GREEN;
+ }
+ }
+ $output .= $prefix . $level . ':';
+ if ($show_types) {
+ $output .= BLUE if (-t STDOUT && $color);
+ $output .= "$type:";
+ }
+ $output .= RESET if (-t STDOUT && $color);
+ $output .= ' ' . $msg . "\n";
+
+ if ($showfile) {
+ my @lines = split("\n", $output, -1);
+ splice(@lines, 1, 1);
+ $output = join("\n", @lines);
+ }
+ $output = (split('\n', $output))[0] . "\n" if ($terse);
+
+ push(our @report, $output);
+
+ return 1;
+}
+
+sub report_dump {
+ our @report;
+}
+
+sub fixup_current_range {
+ my ($lineRef, $offset, $length) = @_;
+
+ if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) {
+ my $o = $1;
+ my $l = $2;
+ my $no = $o + $offset;
+ my $nl = $l + $length;
+ $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/;
+ }
+}
+
+sub fix_inserted_deleted_lines {
+ my ($linesRef, $insertedRef, $deletedRef) = @_;
+
+ my $range_last_linenr = 0;
+ my $delta_offset = 0;
+
+ my $old_linenr = 0;
+ my $new_linenr = 0;
+
+ my $next_insert = 0;
+ my $next_delete = 0;
+
+ my @lines = ();
+
+ my $inserted = @{$insertedRef}[$next_insert++];
+ my $deleted = @{$deletedRef}[$next_delete++];
+
+ foreach my $old_line (@{$linesRef}) {
+ my $save_line = 1;
+ my $line = $old_line; #don't modify the array
+ if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename
+ $delta_offset = 0;
+ } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk
+ $range_last_linenr = $new_linenr;
+ fixup_current_range(\$line, $delta_offset, 0);
+ }
+
+ while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) {
+ $deleted = @{$deletedRef}[$next_delete++];
+ $save_line = 0;
+ fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1);
+ }
+
+ while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) {
+ push(@lines, ${$inserted}{'LINE'});
+ $inserted = @{$insertedRef}[$next_insert++];
+ $new_linenr++;
+ fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1);
+ }
+
+ if ($save_line) {
+ push(@lines, $line);
+ $new_linenr++;
+ }
+
+ $old_linenr++;
+ }
+
+ return @lines;
+}
+
+sub fix_insert_line {
+ my ($linenr, $line) = @_;
+
+ my $inserted = {
+ LINENR => $linenr,
+ LINE => $line,
+ };
+ push(@fixed_inserted, $inserted);
+}
+
+sub fix_delete_line {
+ my ($linenr, $line) = @_;
+
+ my $deleted = {
+ LINENR => $linenr,
+ LINE => $line,
+ };
+
+ push(@fixed_deleted, $deleted);
+}
+
+sub ERROR {
+ my ($type, $msg) = @_;
+
+ if (report("ERROR", $type, $msg)) {
+ our $clean = 0;
+ our $cnt_error++;
+ return 1;
+ }
+ return 0;
+}
+sub WARN {
+ my ($type, $msg) = @_;
+
+ if (report("WARNING", $type, $msg)) {
+ our $clean = 0;
+ our $cnt_warn++;
+ return 1;
+ }
+ return 0;
+}
+sub CHK {
+ my ($type, $msg) = @_;
+
+ if ($check && report("CHECK", $type, $msg)) {
+ our $clean = 0;
+ our $cnt_chk++;
+ return 1;
+ }
+ return 0;
+}
+
+sub check_absolute_file {
+ my ($absolute, $herecurr) = @_;
+ my $file = $absolute;
+
+ ##print "absolute<$absolute>\n";
+
+ # See if any suffix of this path is a path within the tree.
+ while ($file =~ s@^[^/]*/@@) {
+ if (-f "$root/$file") {
+ ##print "file<$file>\n";
+ last;
+ }
+ }
+ if (! -f _) {
+ return 0;
+ }
+
+ # It is, so see if the prefix is acceptable.
+ my $prefix = $absolute;
+ substr($prefix, -length($file)) = '';
+
+ ##print "prefix<$prefix>\n";
+ if ($prefix ne ".../") {
+ WARN("USE_RELATIVE_PATH",
+ "use relative pathname instead of absolute in changelog text\n" . $herecurr);
+ }
+}
+
+sub trim {
+ my ($string) = @_;
+
+ $string =~ s/^\s+|\s+$//g;
+
+ return $string;
+}
+
+sub ltrim {
+ my ($string) = @_;
+
+ $string =~ s/^\s+//;
+
+ return $string;
+}
+
+sub rtrim {
+ my ($string) = @_;
+
+ $string =~ s/\s+$//;
+
+ return $string;
+}
+
+sub string_find_replace {
+ my ($string, $find, $replace) = @_;
+
+ $string =~ s/$find/$replace/g;
+
+ return $string;
+}
+
+sub tabify {
+ my ($leading) = @_;
+
+ my $source_indent = 8;
+ my $max_spaces_before_tab = $source_indent - 1;
+ my $spaces_to_tab = " " x $source_indent;
+
+ #convert leading spaces to tabs
+ 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g;
+ #Remove spaces before a tab
+ 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g;
+
+ return "$leading";
+}
+
+sub pos_last_openparen {
+ my ($line) = @_;
+
+ my $pos = 0;
+
+ my $opens = $line =~ tr/\(/\(/;
+ my $closes = $line =~ tr/\)/\)/;
+
+ my $last_openparen = 0;
+
+ if (($opens == 0) || ($closes >= $opens)) {
+ return -1;
+ }
+
+ my $len = length($line);
+
+ for ($pos = 0; $pos < $len; $pos++) {
+ my $string = substr($line, $pos);
+ if ($string =~ /^($FuncArg|$balanced_parens)/) {
+ $pos += length($1) - 1;
+ } elsif (substr($line, $pos, 1) eq '(') {
+ $last_openparen = $pos;
+ } elsif (index($string, '(') == -1) {
+ last;
+ }
+ }
+
+ return length(expand_tabs(substr($line, 0, $last_openparen))) + 1;
+}
+
+sub process {
+ my $filename = shift;
+
+ my $linenr=0;
+ my $prevline="";
+ my $prevrawline="";
+ my $stashline="";
+ my $stashrawline="";
+
+ my $length;
+ my $indent;
+ my $previndent=0;
+ my $stashindent=0;
+
+ our $clean = 1;
+ my $signoff = 0;
+ my $is_patch = 0;
+ my $in_header_lines = $file ? 0 : 1;
+ my $in_commit_log = 0; #Scanning lines before patch
+ my $has_commit_log = 0; #Encountered lines before patch
+ my $commit_log_possible_stack_dump = 0;
+ my $commit_log_long_line = 0;
+ my $commit_log_has_diff = 0;
+ my $reported_maintainer_file = 0;
+ my $non_utf8_charset = 0;
+
+ my $last_blank_line = 0;
+ my $last_coalesced_string_linenr = -1;
+
+ our @report = ();
+ our $cnt_lines = 0;
+ our $cnt_error = 0;
+ our $cnt_warn = 0;
+ our $cnt_chk = 0;
+
+ # Trace the real file/line as we go.
+ my $realfile = '';
+ my $realline = 0;
+ my $realcnt = 0;
+ my $here = '';
+ my $in_comment = 0;
+ my $comment_edge = 0;
+ my $first_line = 0;
+ my $p1_prefix = '';
+
+ my $prev_values = 'E';
+
+ # suppression flags
+ my %suppress_ifbraces;
+ my %suppress_whiletrailers;
+ my %suppress_export;
+ my $suppress_statement = 0;
+
+ my %signatures = ();
+
+ # Pre-scan the patch sanitizing the lines.
+ # Pre-scan the patch looking for any __setup documentation.
+ #
+ my @setup_docs = ();
+ my $setup_docs = 0;
+
+ my $camelcase_file_seeded = 0;
+
+ sanitise_line_reset();
+ my $line;
+ foreach my $rawline (@rawlines) {
+ $linenr++;
+ $line = $rawline;
+
+ push(@fixed, $rawline) if ($fix);
+
+ if ($rawline=~/^\+\+\+\s+(\S+)/) {
+ $setup_docs = 0;
+ if ($1 =~ m@Documentation/kernel-parameters.txt$@) {
+ $setup_docs = 1;
+ }
+ #next;
+ }
+ if ($rawline=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) {
+ $realline=$1-1;
+ if (defined $2) {
+ $realcnt=$3+1;
+ } else {
+ $realcnt=1+1;
+ }
+ $in_comment = 0;
+
+ # Guestimate if this is a continuing comment. Run
+ # the context looking for a comment "edge". If this
+ # edge is a close comment then we must be in a comment
+ # at context start.
+ my $edge;
+ my $cnt = $realcnt;
+ for (my $ln = $linenr + 1; $cnt > 0; $ln++) {
+ next if (defined $rawlines[$ln - 1] &&
+ $rawlines[$ln - 1] =~ /^-/);
+ $cnt--;
+ #print "RAW<$rawlines[$ln - 1]>\n";
+ last if (!defined $rawlines[$ln - 1]);
+ if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ &&
+ $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) {
+ ($edge) = $1;
+ last;
+ }
+ }
+ if (defined $edge && $edge eq '*/') {
+ $in_comment = 1;
+ }
+
+ # Guestimate if this is a continuing comment. If this
+ # is the start of a diff block and this line starts
+ # ' *' then it is very likely a comment.
+ if (!defined $edge &&
+ $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@)
+ {
+ $in_comment = 1;
+ }
+
+ ##print "COMMENT:$in_comment edge<$edge> $rawline\n";
+ sanitise_line_reset($in_comment);
+
+ } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) {
+ # Standardise the strings and chars within the input to
+ # simplify matching -- only bother with positive lines.
+ $line = sanitise_line($rawline);
+ }
+ push(@lines, $line);
+
+ if ($realcnt > 1) {
+ $realcnt-- if ($line =~ /^(?:\+| |$)/);
+ } else {
+ $realcnt = 0;
+ }
+
+ #print "==>$rawline\n";
+ #print "-->$line\n";
+
+ if ($setup_docs && $line =~ /^\+/) {
+ push(@setup_docs, $line);
+ }
+ }
+
+ $prefix = '';
+
+ $realcnt = 0;
+ $linenr = 0;
+ $fixlinenr = -1;
+ foreach my $line (@lines) {
+ $linenr++;
+ $fixlinenr++;
+ my $sline = $line; #copy of $line
+ $sline =~ s/$;/ /g; #with comments as spaces
+
+ my $rawline = $rawlines[$linenr - 1];
+
+#extract the line range in the file after the patch is applied
+ if (!$in_commit_log &&
+ $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) {
+ $is_patch = 1;
+ $first_line = $linenr + 1;
+ $realline=$1-1;
+ if (defined $2) {
+ $realcnt=$3+1;
+ } else {
+ $realcnt=1+1;
+ }
+ annotate_reset();
+ $prev_values = 'E';
+
+ %suppress_ifbraces = ();
+ %suppress_whiletrailers = ();
+ %suppress_export = ();
+ $suppress_statement = 0;
+ next;
+
+# track the line number as we move through the hunk, note that
+# new versions of GNU diff omit the leading space on completely
+# blank context lines so we need to count that too.
+ } elsif ($line =~ /^( |\+|$)/) {
+ $realline++;
+ $realcnt-- if ($realcnt != 0);
+
+ # Measure the line length and indent.
+ ($length, $indent) = line_stats($rawline);
+
+ # Track the previous line.
+ ($prevline, $stashline) = ($stashline, $line);
+ ($previndent, $stashindent) = ($stashindent, $indent);
+ ($prevrawline, $stashrawline) = ($stashrawline, $rawline);
+
+ #warn "line<$line>\n";
+
+ } elsif ($realcnt == 1) {
+ $realcnt--;
+ }
+
+ my $hunk_line = ($realcnt != 0);
+
+ $here = "#$linenr: " if (!$file);
+ $here = "#$realline: " if ($file);
+
+ my $found_file = 0;
+ # extract the filename as it passes
+ if ($line =~ /^diff --git.*?(\S+)$/) {
+ $realfile = $1;
+ $realfile =~ s@^([^/]*)/@@ if (!$file);
+ $in_commit_log = 0;
+ $found_file = 1;
+ } elsif ($line =~ /^\+\+\+\s+(\S+)/) {
+ $realfile = $1;
+ $realfile =~ s@^([^/]*)/@@ if (!$file);
+ $in_commit_log = 0;
+
+ $p1_prefix = $1;
+ if (!$file && $tree && $p1_prefix ne '' &&
+ -e "$root/$p1_prefix") {
+ WARN("PATCH_PREFIX",
+ "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n");
+ }
+
+ if ($realfile =~ m@^include/asm/@) {
+ ERROR("MODIFIED_INCLUDE_ASM",
+ "do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n");
+ }
+ $found_file = 1;
+ }
+
+#make up the handle for any error we report on this line
+ if ($showfile) {
+ $prefix = "$realfile:$realline: "
+ } elsif ($emacs) {
+ if ($file) {
+ $prefix = "$filename:$realline: ";
+ } else {
+ $prefix = "$filename:$linenr: ";
+ }
+ }
+
+ if ($found_file) {
+ if (is_maintained_obsolete($realfile)) {
+ WARN("OBSOLETE",
+ "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n");
+ }
+ if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) {
+ $check = 1;
+ } else {
+ $check = $check_orig;
+ }
+ next;
+ }
+
+ $here .= "FILE: $realfile:$realline:" if ($realcnt != 0);
+
+ my $hereline = "$here\n$rawline\n";
+ my $herecurr = "$here\n$rawline\n";
+ my $hereprev = "$here\n$prevrawline\n$rawline\n";
+
+ $cnt_lines++ if ($realcnt != 0);
+
+# Check if the commit log has what seems like a diff which can confuse patch
+ if ($in_commit_log && !$commit_log_has_diff &&
+ (($line =~ m@^\s+diff\b.*a/[\w/]+@ &&
+ $line =~ m@^\s+diff\b.*a/([\w/]+)\s+b/$1\b@) ||
+ $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ ||
+ $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) {
+ ERROR("DIFF_IN_COMMIT_MSG",
+ "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr);
+ $commit_log_has_diff = 1;
+ }
+
+# Check for incorrect file permissions
+ if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) {
+ my $permhere = $here . "FILE: $realfile\n";
+ if ($realfile !~ m@scripts/@ &&
+ $realfile !~ /\.(py|pl|awk|sh)$/) {
+ ERROR("EXECUTE_PERMISSIONS",
+ "do not set execute permissions for source files\n" . $permhere);
+ }
+ }
+
+# Check the patch for a signoff:
+ if ($line =~ /^\s*signed-off-by:/i) {
+ $signoff++;
+ $in_commit_log = 0;
+ }
+
+# Check if MAINTAINERS is being updated. If so, there's probably no need to
+# emit the "does MAINTAINERS need updating?" message on file add/move/delete
+ if ($line =~ /^\s*MAINTAINERS\s*\|/) {
+ $reported_maintainer_file = 1;
+ }
+
+# Check signature styles
+ if (!$in_header_lines &&
+ $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) {
+ my $space_before = $1;
+ my $sign_off = $2;
+ my $space_after = $3;
+ my $email = $4;
+ my $ucfirst_sign_off = ucfirst(lc($sign_off));
+
+ if ($sign_off !~ /$signature_tags/) {
+ WARN("BAD_SIGN_OFF",
+ "Non-standard signature: $sign_off\n" . $herecurr);
+ }
+ if (defined $space_before && $space_before ne "") {
+ if (WARN("BAD_SIGN_OFF",
+ "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =
+ "$ucfirst_sign_off $email";
+ }
+ }
+ if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) {
+ if (WARN("BAD_SIGN_OFF",
+ "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =
+ "$ucfirst_sign_off $email";
+ }
+
+ }
+ if (!defined $space_after || $space_after ne " ") {
+ if (WARN("BAD_SIGN_OFF",
+ "Use a single space after $ucfirst_sign_off\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =
+ "$ucfirst_sign_off $email";
+ }
+ }
+
+ my ($email_name, $email_address, $comment) = parse_email($email);
+ my $suggested_email = format_email(($email_name, $email_address));
+ if ($suggested_email eq "") {
+ ERROR("BAD_SIGN_OFF",
+ "Unrecognized email address: '$email'\n" . $herecurr);
+ } else {
+ my $dequoted = $suggested_email;
+ $dequoted =~ s/^"//;
+ $dequoted =~ s/" </ </;
+ # Don't force email to have quotes
+ # Allow just an angle bracketed address
+ if ("$dequoted$comment" ne $email &&
+ "<$email_address>$comment" ne $email &&
+ "$suggested_email$comment" ne $email) {
+ WARN("BAD_SIGN_OFF",
+ "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr);
+ }
+ }
+
+# Check for duplicate signatures
+ my $sig_nospace = $line;
+ $sig_nospace =~ s/\s//g;
+ $sig_nospace = lc($sig_nospace);
+ if (defined $signatures{$sig_nospace}) {
+ WARN("BAD_SIGN_OFF",
+ "Duplicate signature\n" . $herecurr);
+ } else {
+ $signatures{$sig_nospace} = 1;
+ }
+ }
+
+# Check email subject for common tools that don't need to be mentioned
+ if ($in_header_lines &&
+ $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) {
+ WARN("EMAIL_SUBJECT",
+ "A patch subject line should describe the change not the tool that found it\n" . $herecurr);
+ }
+
+# Check for old stable address
+ if ($line =~ /^\s*cc:\s*.*<?\bstable\@kernel\.org\b>?.*$/i) {
+ ERROR("STABLE_ADDRESS",
+ "The 'stable' address should be 'stable\@vger.kernel.org'\n" . $herecurr);
+ }
+
+# Check for unwanted Gerrit info
+ if ($in_commit_log && $line =~ /^\s*change-id:/i) {
+ ERROR("GERRIT_CHANGE_ID",
+ "Remove Gerrit Change-Id's before submitting upstream.\n" . $herecurr);
+ }
+
+# Check if the commit log is in a possible stack dump
+ if ($in_commit_log && !$commit_log_possible_stack_dump &&
+ ($line =~ /^\s*(?:WARNING:|BUG:)/ ||
+ $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ ||
+ # timestamp
+ $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/)) {
+ # stack dump address
+ $commit_log_possible_stack_dump = 1;
+ }
+
+# Check for line lengths > 75 in commit log, warn once
+ if ($in_commit_log && !$commit_log_long_line &&
+ length($line) > 75 &&
+ !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ ||
+ # file delta changes
+ $line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ ||
+ # filename then :
+ $line =~ /^\s*(?:Fixes:|Link:)/i ||
+ # A Fixes: or Link: line
+ $commit_log_possible_stack_dump)) {
+ WARN("COMMIT_LOG_LONG_LINE",
+ "Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr);
+ $commit_log_long_line = 1;
+ }
+
+# Reset possible stack dump if a blank line is found
+ if ($in_commit_log && $commit_log_possible_stack_dump &&
+ $line =~ /^\s*$/) {
+ $commit_log_possible_stack_dump = 0;
+ }
+
+# Check for git id commit length and improperly formed commit descriptions
+ if ($in_commit_log && !$commit_log_possible_stack_dump &&
+ $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink):/i &&
+ ($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i ||
+ ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i &&
+ $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i &&
+ $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) {
+ my $init_char = "c";
+ my $orig_commit = "";
+ my $short = 1;
+ my $long = 0;
+ my $case = 1;
+ my $space = 1;
+ my $hasdesc = 0;
+ my $hasparens = 0;
+ my $id = '0123456789ab';
+ my $orig_desc = "commit description";
+ my $description = "";
+
+ if ($line =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) {
+ $init_char = $1;
+ $orig_commit = lc($2);
+ } elsif ($line =~ /\b([0-9a-f]{12,40})\b/i) {
+ $orig_commit = lc($1);
+ }
+
+ $short = 0 if ($line =~ /\bcommit\s+[0-9a-f]{12,40}/i);
+ $long = 1 if ($line =~ /\bcommit\s+[0-9a-f]{41,}/i);
+ $space = 0 if ($line =~ /\bcommit [0-9a-f]/i);
+ $case = 0 if ($line =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/);
+ if ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)"\)/i) {
+ $orig_desc = $1;
+ $hasparens = 1;
+ } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s*$/i &&
+ defined $rawlines[$linenr] &&
+ $rawlines[$linenr] =~ /^\s*\("([^"]+)"\)/) {
+ $orig_desc = $1;
+ $hasparens = 1;
+ } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("[^"]+$/i &&
+ defined $rawlines[$linenr] &&
+ $rawlines[$linenr] =~ /^\s*[^"]+"\)/) {
+ $line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)$/i;
+ $orig_desc = $1;
+ $rawlines[$linenr] =~ /^\s*([^"]+)"\)/;
+ $orig_desc .= " " . $1;
+ $hasparens = 1;
+ }
+
+ ($id, $description) = git_commit_info($orig_commit,
+ $id, $orig_desc);
+
+ if ($short || $long || $space || $case || ($orig_desc ne $description) || !$hasparens) {
+ ERROR("GIT_COMMIT_ID",
+ "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herecurr);
+ }
+ }
+
+# Check for added, moved or deleted files
+ if (!$reported_maintainer_file && !$in_commit_log &&
+ ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ ||
+ $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ ||
+ ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ &&
+ (defined($1) || defined($2))))) {
+ $reported_maintainer_file = 1;
+ WARN("FILE_PATH_CHANGES",
+ "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr);
+ }
+
+# Check for wrappage within a valid hunk of the file
+ if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) {
+ ERROR("CORRUPTED_PATCH",
+ "patch seems to be corrupt (line wrapped?)\n" .
+ $herecurr) if (!$emitted_corrupt++);
+ }
+
+# Check for absolute kernel paths.
+ if ($tree) {
+ while ($line =~ m{(?:^|\s)(/\S*)}g) {
+ my $file = $1;
+
+ if ($file =~ m{^(.*?)(?::\d+)+:?$} &&
+ check_absolute_file($1, $herecurr)) {
+ #
+ } else {
+ check_absolute_file($file, $herecurr);
+ }
+ }
+ }
+
+# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php
+ if (($realfile =~ /^$/ || $line =~ /^\+/) &&
+ $rawline !~ m/^$UTF8*$/) {
+ my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/);
+
+ my $blank = copy_spacing($rawline);
+ my $ptr = substr($blank, 0, length($utf8_prefix)) . "^";
+ my $hereptr = "$hereline$ptr\n";
+
+ CHK("INVALID_UTF8",
+ "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr);
+ }
+
+# Check if it's the start of a commit log
+# (not a header line and we haven't seen the patch filename)
+ if ($in_header_lines && $realfile =~ /^$/ &&
+ !($rawline =~ /^\s+\S/ ||
+ $rawline =~ /^(commit\b|from\b|[\w-]+:).*$/i)) {
+ $in_header_lines = 0;
+ $in_commit_log = 1;
+ $has_commit_log = 1;
+ }
+
+# Check if there is UTF-8 in a commit log when a mail header has explicitly
+# declined it, i.e defined some charset where it is missing.
+ if ($in_header_lines &&
+ $rawline =~ /^Content-Type:.+charset="(.+)".*$/ &&
+ $1 !~ /utf-8/i) {
+ $non_utf8_charset = 1;
+ }
+
+ if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ &&
+ $rawline =~ /$NON_ASCII_UTF8/) {
+ WARN("UTF8_BEFORE_PATCH",
+ "8-bit UTF-8 used in possible commit log\n" . $herecurr);
+ }
+
+# Check for various typo / spelling mistakes
+ if (defined($misspellings) &&
+ ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) {
+ while ($rawline =~ /(?:^|[^a-z@])($misspellings)(?:\b|$|[^a-z@])/gi) {
+ my $typo = $1;
+ my $typo_fix = $spelling_fix{lc($typo)};
+ $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/);
+ $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/);
+ my $msg_type = \&WARN;
+ $msg_type = \&CHK if ($file);
+ if (&{$msg_type}("TYPO_SPELLING",
+ "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/;
+ }
+ }
+ }
+
+# ignore non-hunk lines and lines being removed
+ next if (!$hunk_line || $line =~ /^-/);
+
+#trailing whitespace
+ if ($line =~ /^\+.*\015/) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ if (ERROR("DOS_LINE_ENDINGS",
+ "DOS line endings\n" . $herevet) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/[\s\015]+$//;
+ }
+ } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ if (ERROR("TRAILING_WHITESPACE",
+ "trailing whitespace\n" . $herevet) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\s+$//;
+ }
+
+ $rpt_cleaners = 1;
+ }
+
+# Check for FSF mailing addresses.
+ if ($rawline =~ /\bwrite to the Free/i ||
+ $rawline =~ /\b59\s+Temple\s+Pl/i ||
+ $rawline =~ /\b51\s+Franklin\s+St/i) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ my $msg_type = \&ERROR;
+ $msg_type = \&CHK if ($file);
+ &{$msg_type}("FSF_MAILING_ADDRESS",
+ "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet)
+ }
+
+# check for Kconfig help text having a real description
+# Only applies when adding the entry originally, after that we do not have
+# sufficient context to determine whether it is indeed long enough.
+ if ($realfile =~ /Kconfig/ &&
+ $line =~ /^\+\s*config\s+/) {
+ my $length = 0;
+ my $cnt = $realcnt;
+ my $ln = $linenr + 1;
+ my $f;
+ my $is_start = 0;
+ my $is_end = 0;
+ for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
+ $f = $lines[$ln - 1];
+ $cnt-- if ($lines[$ln - 1] !~ /^-/);
+ $is_end = $lines[$ln - 1] =~ /^\+/;
+
+ next if ($f =~ /^-/);
+ last if (!$file && $f =~ /^\@\@/);
+
+ if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate)\s*\"/) {
+ $is_start = 1;
+ } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
+ $length = -1;
+ }
+
+ $f =~ s/^.//;
+ $f =~ s/#.*//;
+ $f =~ s/^\s+//;
+ next if ($f =~ /^$/);
+ if ($f =~ /^\s*config\s/) {
+ $is_end = 1;
+ last;
+ }
+ $length++;
+ }
+ if ($is_start && $is_end && $length < $min_conf_desc_length) {
+ WARN("CONFIG_DESCRIPTION",
+ "please write a paragraph that describes the config symbol fully\n" . $herecurr);
+ }
+ #print "is_start<$is_start> is_end<$is_end> length<$length>\n";
+ }
+
+# discourage the addition of CONFIG_EXPERIMENTAL in Kconfig.
+ if ($realfile =~ /Kconfig/ &&
+ $line =~ /.\s*depends on\s+.*\bEXPERIMENTAL\b/) {
+ WARN("CONFIG_EXPERIMENTAL",
+ "Use of CONFIG_EXPERIMENTAL is deprecated. For alternatives, see https://lkml.org/lkml/2012/10/23/580\n");
+ }
+
+# discourage the use of boolean for type definition attributes of Kconfig options
+ if ($realfile =~ /Kconfig/ &&
+ $line =~ /^\+\s*\bboolean\b/) {
+ WARN("CONFIG_TYPE_BOOLEAN",
+ "Use of boolean is deprecated, please use bool instead.\n" . $herecurr);
+ }
+
+ if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) &&
+ ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) {
+ my $flag = $1;
+ my $replacement = {
+ 'EXTRA_AFLAGS' => 'asflags-y',
+ 'EXTRA_CFLAGS' => 'ccflags-y',
+ 'EXTRA_CPPFLAGS' => 'cppflags-y',
+ 'EXTRA_LDFLAGS' => 'ldflags-y',
+ };
+
+ WARN("DEPRECATED_VARIABLE",
+ "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag});
+ }
+
+# check for DT compatible documentation
+ if (defined $root &&
+ (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) ||
+ ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) {
+
+ my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g;
+
+ my $dt_path = $root . "/Documentation/devicetree/bindings/";
+ my $vp_file = $dt_path . "vendor-prefixes.txt";
+
+ foreach my $compat (@compats) {
+ my $compat2 = $compat;
+ $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/;
+ my $compat3 = $compat;
+ $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/;
+ `grep -Erq "$compat|$compat2|$compat3" $dt_path`;
+ if ( $? >> 8 ) {
+ WARN("UNDOCUMENTED_DT_STRING",
+ "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr);
+ }
+
+ next if $compat !~ /^([a-zA-Z0-9\-]+)\,/;
+ my $vendor = $1;
+ `grep -Eq "^$vendor\\b" $vp_file`;
+ if ( $? >> 8 ) {
+ WARN("UNDOCUMENTED_DT_STRING",
+ "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr);
+ }
+ }
+ }
+
+# check we are in a valid source file if not then ignore this hunk
+ next if ($realfile !~ /\.(h|c|s|S|pl|sh|dtsi|dts)$/);
+
+# line length limit (with some exclusions)
+#
+# There are a few types of lines that may extend beyond $max_line_length:
+# logging functions like pr_info that end in a string
+# lines with a single string
+# #defines that are a single string
+#
+# There are 3 different line length message types:
+# LONG_LINE_COMMENT a comment starts before but extends beyond $max_linelength
+# LONG_LINE_STRING a string starts before but extends beyond $max_line_length
+# LONG_LINE all other lines longer than $max_line_length
+#
+# if LONG_LINE is ignored, the other 2 types are also ignored
+#
+
+ if ($line =~ /^\+/ && $length > $max_line_length) {
+ my $msg_type = "LONG_LINE";
+
+ # Check the allowed long line types first
+
+ # logging functions that end in a string that starts
+ # before $max_line_length
+ if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ &&
+ length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) {
+ $msg_type = "";
+
+ # lines with only strings (w/ possible termination)
+ # #defines with only strings
+ } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ ||
+ $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) {
+ $msg_type = "";
+
+ # EFI_GUID is another special case
+ } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/) {
+ $msg_type = "";
+
+ # Otherwise set the alternate message types
+
+ # a comment starts before $max_line_length
+ } elsif ($line =~ /($;[\s$;]*)$/ &&
+ length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) {
+ $msg_type = "LONG_LINE_COMMENT"
+
+ # a quoted string starts before $max_line_length
+ } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ &&
+ length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) {
+ $msg_type = "LONG_LINE_STRING"
+ }
+
+ if ($msg_type ne "" &&
+ (show_type("LONG_LINE") || show_type($msg_type))) {
+ WARN($msg_type,
+ "line over $max_line_length characters\n" . $herecurr);
+ }
+ }
+
+# check for adding lines without a newline.
+ if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) {
+ WARN("MISSING_EOF_NEWLINE",
+ "adding a line without newline at end of file\n" . $herecurr);
+ }
+
+# Blackfin: use hi/lo macros
+ if ($realfile =~ m@arch/blackfin/.*\.S$@) {
+ if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("LO_MACRO",
+ "use the LO() macro, not (... & 0xFFFF)\n" . $herevet);
+ }
+ if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("HI_MACRO",
+ "use the HI() macro, not (... >> 16)\n" . $herevet);
+ }
+ }
+
+# check we are in a valid source file C or perl if not then ignore this hunk
+ next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/);
+
+# at the beginning of a line any tabs must come first and anything
+# more than 8 must use tabs.
+ if ($rawline =~ /^\+\s* \t\s*\S/ ||
+ $rawline =~ /^\+\s* \s*/) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ $rpt_cleaners = 1;
+ if (ERROR("CODE_INDENT",
+ "code indent should use tabs where possible\n" . $herevet) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e;
+ }
+ }
+
+# check for space before tabs.
+ if ($rawline =~ /^\+/ && $rawline =~ / \t/) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ if (WARN("SPACE_BEFORE_TAB",
+ "please, no space before tabs\n" . $herevet) &&
+ $fix) {
+ while ($fixed[$fixlinenr] =~
+ s/(^\+.*) {8,8}\t/$1\t\t/) {}
+ while ($fixed[$fixlinenr] =~
+ s/(^\+.*) +\t/$1\t/) {}
+ }
+ }
+
+# check for && or || at the start of a line
+ if ($rawline =~ /^\+\s*(&&|\|\|)/) {
+ CHK("LOGICAL_CONTINUATIONS",
+ "Logical continuations should be on the previous line\n" . $hereprev);
+ }
+
+# check indentation starts on a tab stop
+ if ($^V && $^V ge 5.10.0 &&
+ $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$))/) {
+ my $indent = length($1);
+ if ($indent % 8) {
+ if (WARN("TABSTOP",
+ "Statements should start on a tabstop\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/8)@e;
+ }
+ }
+ }
+
+# check multi-line statement indentation matches previous line
+ if ($^V && $^V ge 5.10.0 &&
+ $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|$Ident\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) {
+ $prevline =~ /^\+(\t*)(.*)$/;
+ my $oldindent = $1;
+ my $rest = $2;
+
+ my $pos = pos_last_openparen($rest);
+ if ($pos >= 0) {
+ $line =~ /^(\+| )([ \t]*)/;
+ my $newindent = $2;
+
+ my $goodtabindent = $oldindent .
+ "\t" x ($pos / 8) .
+ " " x ($pos % 8);
+ my $goodspaceindent = $oldindent . " " x $pos;
+
+ if ($newindent ne $goodtabindent &&
+ $newindent ne $goodspaceindent) {
+
+ if (CHK("PARENTHESIS_ALIGNMENT",
+ "Alignment should match open parenthesis\n" . $hereprev) &&
+ $fix && $line =~ /^\+/) {
+ $fixed[$fixlinenr] =~
+ s/^\+[ \t]*/\+$goodtabindent/;
+ }
+ }
+ }
+ }
+
+# check for space after cast like "(int) foo" or "(struct foo) bar"
+# avoid checking a few false positives:
+# "sizeof(<type>)" or "__alignof__(<type>)"
+# function pointer declarations like "(*foo)(int) = bar;"
+# structure definitions like "(struct foo) { 0 };"
+# multiline macros that define functions
+# known attributes or the __attribute__ keyword
+ if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ &&
+ (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) {
+ if (CHK("SPACING",
+ "No space is necessary after a cast\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/(\(\s*$Type\s*\))[ \t]+/$1/;
+ }
+ }
+
+# Block comment styles
+# Networking with an initial /*
+ if ($realfile =~ m@^(drivers/net/|net/)@ &&
+ $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ &&
+ $rawline =~ /^\+[ \t]*\*/ &&
+ $realline > 2) {
+ WARN("NETWORKING_BLOCK_COMMENT_STYLE",
+ "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev);
+ }
+
+# Block comments use * on subsequent lines
+ if ($prevline =~ /$;[ \t]*$/ && #ends in comment
+ $prevrawline =~ /^\+.*?\/\*/ && #starting /*
+ $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */
+ $rawline =~ /^\+/ && #line is new
+ $rawline !~ /^\+[ \t]*\*/) { #no leading *
+ WARN("BLOCK_COMMENT_STYLE",
+ "Block comments use * on subsequent lines\n" . $hereprev);
+ }
+
+# Block comments use */ on trailing lines
+ if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */
+ $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/
+ $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/
+ $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */
+ WARN("BLOCK_COMMENT_STYLE",
+ "Block comments use a trailing */ on a separate line\n" . $herecurr);
+ }
+
+# Block comment * alignment
+ if ($prevline =~ /$;[ \t]*$/ && #ends in comment
+ $line =~ /^\+[ \t]*$;/ && #leading comment
+ $rawline =~ /^\+[ \t]*\*/ && #leading *
+ (($prevrawline =~ /^\+.*?\/\*/ && #leading /*
+ $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */
+ $prevrawline =~ /^\+[ \t]*\*/)) { #leading *
+ my $oldindent;
+ $prevrawline =~ m@^\+([ \t]*/?)\*@;
+ if (defined($1)) {
+ $oldindent = expand_tabs($1);
+ } else {
+ $prevrawline =~ m@^\+(.*/?)\*@;
+ $oldindent = expand_tabs($1);
+ }
+ $rawline =~ m@^\+([ \t]*)\*@;
+ my $newindent = $1;
+ $newindent = expand_tabs($newindent);
+ if (length($oldindent) ne length($newindent)) {
+ WARN("BLOCK_COMMENT_STYLE",
+ "Block comments should align the * on each line\n" . $hereprev);
+ }
+ }
+
+# check for missing blank lines after struct/union declarations
+# with exceptions for various attributes and macros
+ if ($prevline =~ /^[\+ ]};?\s*$/ &&
+ $line =~ /^\+/ &&
+ !($line =~ /^\+\s*$/ ||
+ $line =~ /^\+\s*EXPORT_SYMBOL/ ||
+ $line =~ /^\+\s*MODULE_/i ||
+ $line =~ /^\+\s*\#\s*(?:end|elif|else)/ ||
+ $line =~ /^\+[a-z_]*init/ ||
+ $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ ||
+ $line =~ /^\+\s*DECLARE/ ||
+ $line =~ /^\+\s*__setup/)) {
+ if (CHK("LINE_SPACING",
+ "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) &&
+ $fix) {
+ fix_insert_line($fixlinenr, "\+");
+ }
+ }
+
+# check for multiple consecutive blank lines
+ if ($prevline =~ /^[\+ ]\s*$/ &&
+ $line =~ /^\+\s*$/ &&
+ $last_blank_line != ($linenr - 1)) {
+ if (CHK("LINE_SPACING",
+ "Please don't use multiple blank lines\n" . $hereprev) &&
+ $fix) {
+ fix_delete_line($fixlinenr, $rawline);
+ }
+
+ $last_blank_line = $linenr;
+ }
+
+# check for missing blank lines after declarations
+ if ($sline =~ /^\+\s+\S/ && #Not at char 1
+ # actual declarations
+ ($prevline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ ||
+ # function pointer declarations
+ $prevline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ ||
+ # foo bar; where foo is some local typedef or #define
+ $prevline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ ||
+ # known declaration macros
+ $prevline =~ /^\+\s+$declaration_macros/) &&
+ # for "else if" which can look like "$Ident $Ident"
+ !($prevline =~ /^\+\s+$c90_Keywords\b/ ||
+ # other possible extensions of declaration lines
+ $prevline =~ /(?:$Compare|$Assignment|$Operators)\s*$/ ||
+ # not starting a section or a macro "\" extended line
+ $prevline =~ /(?:\{\s*|\\)$/) &&
+ # looks like a declaration
+ !($sline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ ||
+ # function pointer declarations
+ $sline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ ||
+ # foo bar; where foo is some local typedef or #define
+ $sline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ ||
+ # known declaration macros
+ $sline =~ /^\+\s+$declaration_macros/ ||
+ # start of struct or union or enum
+ $sline =~ /^\+\s+(?:union|struct|enum|typedef)\b/ ||
+ # start or end of block or continuation of declaration
+ $sline =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ ||
+ # bitfield continuation
+ $sline =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ ||
+ # other possible extensions of declaration lines
+ $sline =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/) &&
+ # indentation of previous and current line are the same
+ (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/)) {
+ if (WARN("LINE_SPACING",
+ "Missing a blank line after declarations\n" . $hereprev) &&
+ $fix) {
+ fix_insert_line($fixlinenr, "\+");
+ }
+ }
+
+# check for spaces at the beginning of a line.
+# Exceptions:
+# 1) within comments
+# 2) indented preprocessor commands
+# 3) hanging labels
+ if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) {
+ my $herevet = "$here\n" . cat_vet($rawline) . "\n";
+ if (WARN("LEADING_SPACE",
+ "please, no spaces at the start of a line\n" . $herevet) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e;
+ }
+ }
+
+# check we are in a valid C source file if not then ignore this hunk
+ next if ($realfile !~ /\.(h|c)$/);
+
+# check indentation of any line with a bare else
+# (but not if it is a multiple line "if (foo) return bar; else return baz;")
+# if the previous line is a break or return and is indented 1 tab more...
+ if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) {
+ my $tabs = length($1) + 1;
+ if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ ||
+ ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ &&
+ defined $lines[$linenr] &&
+ $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) {
+ WARN("UNNECESSARY_ELSE",
+ "else is not generally useful after a break or return\n" . $hereprev);
+ }
+ }
+
+# check indentation of a line with a break;
+# if the previous line is a goto or return and is indented the same # of tabs
+ if ($sline =~ /^\+([\t]+)break\s*;\s*$/) {
+ my $tabs = $1;
+ if ($prevline =~ /^\+$tabs(?:goto|return)\b/) {
+ WARN("UNNECESSARY_BREAK",
+ "break is not useful after a goto or return\n" . $hereprev);
+ }
+ }
+
+# discourage the addition of CONFIG_EXPERIMENTAL in #if(def).
+ if ($line =~ /^\+\s*\#\s*if.*\bCONFIG_EXPERIMENTAL\b/) {
+ WARN("CONFIG_EXPERIMENTAL",
+ "Use of CONFIG_EXPERIMENTAL is deprecated. For alternatives, see https://lkml.org/lkml/2012/10/23/580\n");
+ }
+
+# check for RCS/CVS revision markers
+ if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) {
+ WARN("CVS_KEYWORD",
+ "CVS style keyword markers, these will _not_ be updated\n". $herecurr);
+ }
+
+# Blackfin: don't use __builtin_bfin_[cs]sync
+ if ($line =~ /__builtin_bfin_csync/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("CSYNC",
+ "use the CSYNC() macro in asm/blackfin.h\n" . $herevet);
+ }
+ if ($line =~ /__builtin_bfin_ssync/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("SSYNC",
+ "use the SSYNC() macro in asm/blackfin.h\n" . $herevet);
+ }
+
+# check for old HOTPLUG __dev<foo> section markings
+ if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) {
+ WARN("HOTPLUG_SECTION",
+ "Using $1 is unnecessary\n" . $herecurr);
+ }
+
+# Check for potential 'bare' types
+ my ($stat, $cond, $line_nr_next, $remain_next, $off_next,
+ $realline_next);
+#print "LINE<$line>\n";
+ if ($linenr >= $suppress_statement &&
+ $realcnt && $sline =~ /.\s*\S/) {
+ ($stat, $cond, $line_nr_next, $remain_next, $off_next) =
+ ctx_statement_block($linenr, $realcnt, 0);
+ $stat =~ s/\n./\n /g;
+ $cond =~ s/\n./\n /g;
+
+#print "linenr<$linenr> <$stat>\n";
+ # If this statement has no statement boundaries within
+ # it there is no point in retrying a statement scan
+ # until we hit end of it.
+ my $frag = $stat; $frag =~ s/;+\s*$//;
+ if ($frag !~ /(?:{|;)/) {
+#print "skip<$line_nr_next>\n";
+ $suppress_statement = $line_nr_next;
+ }
+
+ # Find the real next line.
+ $realline_next = $line_nr_next;
+ if (defined $realline_next &&
+ (!defined $lines[$realline_next - 1] ||
+ substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) {
+ $realline_next++;
+ }
+
+ my $s = $stat;
+ $s =~ s/{.*$//s;
+
+ # Ignore goto labels.
+ if ($s =~ /$Ident:\*$/s) {
+
+ # Ignore functions being called
+ } elsif ($s =~ /^.\s*$Ident\s*\(/s) {
+
+ } elsif ($s =~ /^.\s*else\b/s) {
+
+ # declarations always start with types
+ } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) {
+ my $type = $1;
+ $type =~ s/\s+/ /g;
+ possible($type, "A:" . $s);
+
+ # definitions in global scope can only start with types
+ } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) {
+ possible($1, "B:" . $s);
+ }
+
+ # any (foo ... *) is a pointer cast, and foo is a type
+ while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) {
+ possible($1, "C:" . $s);
+ }
+
+ # Check for any sort of function declaration.
+ # int foo(something bar, other baz);
+ # void (*store_gdt)(x86_descr_ptr *);
+ if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) {
+ my ($name_len) = length($1);
+
+ my $ctx = $s;
+ substr($ctx, 0, $name_len + 1, '');
+ $ctx =~ s/\)[^\)]*$//;
+
+ for my $arg (split(/\s*,\s*/, $ctx)) {
+ if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) {
+
+ possible($1, "D:" . $s);
+ }
+ }
+ }
+
+ }
+
+#
+# Checks which may be anchored in the context.
+#
+
+# Check for switch () and associated case and default
+# statements should be at the same indent.
+ if ($line=~/\bswitch\s*\(.*\)/) {
+ my $err = '';
+ my $sep = '';
+ my @ctx = ctx_block_outer($linenr, $realcnt);
+ shift(@ctx);
+ for my $ctx (@ctx) {
+ my ($clen, $cindent) = line_stats($ctx);
+ if ($ctx =~ /^\+\s*(case\s+|default:)/ &&
+ $indent != $cindent) {
+ $err .= "$sep$ctx\n";
+ $sep = '';
+ } else {
+ $sep = "[...]\n";
+ }
+ }
+ if ($err ne '') {
+ ERROR("SWITCH_CASE_INDENT_LEVEL",
+ "switch and case should be at the same indent\n$hereline$err");
+ }
+ }
+
+# if/while/etc brace do not go on next line, unless defining a do while loop,
+# or if that brace on the next line is for something else
+ if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) {
+ my $pre_ctx = "$1$2";
+
+ my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0);
+
+ if ($line =~ /^\+\t{6,}/) {
+ WARN("DEEP_INDENTATION",
+ "Too many leading tabs - consider code refactoring\n" . $herecurr);
+ }
+
+ my $ctx_cnt = $realcnt - $#ctx - 1;
+ my $ctx = join("\n", @ctx);
+
+ my $ctx_ln = $linenr;
+ my $ctx_skip = $realcnt;
+
+ while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt &&
+ defined $lines[$ctx_ln - 1] &&
+ $lines[$ctx_ln - 1] =~ /^-/)) {
+ ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n";
+ $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/);
+ $ctx_ln++;
+ }
+
+ #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n";
+ #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n";
+
+ if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) {
+ ERROR("OPEN_BRACE",
+ "that open brace { should be on the previous line\n" .
+ "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
+ }
+ if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ &&
+ $ctx =~ /\)\s*\;\s*$/ &&
+ defined $lines[$ctx_ln - 1])
+ {
+ my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]);
+ if ($nindent > $indent) {
+ WARN("TRAILING_SEMICOLON",
+ "trailing semicolon indicates no statements, indent implies otherwise\n" .
+ "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
+ }
+ }
+ }
+
+# Check relative indent for conditionals and blocks.
+ if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) {
+ ($stat, $cond, $line_nr_next, $remain_next, $off_next) =
+ ctx_statement_block($linenr, $realcnt, 0)
+ if (!defined $stat);
+ my ($s, $c) = ($stat, $cond);
+
+ substr($s, 0, length($c), '');
+
+ # remove inline comments
+ $s =~ s/$;/ /g;
+ $c =~ s/$;/ /g;
+
+ # Find out how long the conditional actually is.
+ my @newlines = ($c =~ /\n/gs);
+ my $cond_lines = 1 + $#newlines;
+
+ # Make sure we remove the line prefixes as we have
+ # none on the first line, and are going to readd them
+ # where necessary.
+ $s =~ s/\n./\n/gs;
+ while ($s =~ /\n\s+\\\n/) {
+ $cond_lines += $s =~ s/\n\s+\\\n/\n/g;
+ }
+
+ # We want to check the first line inside the block
+ # starting at the end of the conditional, so remove:
+ # 1) any blank line termination
+ # 2) any opening brace { on end of the line
+ # 3) any do (...) {
+ my $continuation = 0;
+ my $check = 0;
+ $s =~ s/^.*\bdo\b//;
+ $s =~ s/^\s*{//;
+ if ($s =~ s/^\s*\\//) {
+ $continuation = 1;
+ }
+ if ($s =~ s/^\s*?\n//) {
+ $check = 1;
+ $cond_lines++;
+ }
+
+ # Also ignore a loop construct at the end of a
+ # preprocessor statement.
+ if (($prevline =~ /^.\s*#\s*define\s/ ||
+ $prevline =~ /\\\s*$/) && $continuation == 0) {
+ $check = 0;
+ }
+
+ my $cond_ptr = -1;
+ $continuation = 0;
+ while ($cond_ptr != $cond_lines) {
+ $cond_ptr = $cond_lines;
+
+ # If we see an #else/#elif then the code
+ # is not linear.
+ if ($s =~ /^\s*\#\s*(?:else|elif)/) {
+ $check = 0;
+ }
+
+ # Ignore:
+ # 1) blank lines, they should be at 0,
+ # 2) preprocessor lines, and
+ # 3) labels.
+ if ($continuation ||
+ $s =~ /^\s*?\n/ ||
+ $s =~ /^\s*#\s*?/ ||
+ $s =~ /^\s*$Ident\s*:/) {
+ $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0;
+ if ($s =~ s/^.*?\n//) {
+ $cond_lines++;
+ }
+ }
+ }
+
+ my (undef, $sindent) = line_stats("+" . $s);
+ my $stat_real = raw_line($linenr, $cond_lines);
+
+ # Check if either of these lines are modified, else
+ # this is not this patch's fault.
+ if (!defined($stat_real) ||
+ $stat !~ /^\+/ && $stat_real !~ /^\+/) {
+ $check = 0;
+ }
+ if (defined($stat_real) && $cond_lines > 1) {
+ $stat_real = "[...]\n$stat_real";
+ }
+
+ #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n";
+
+ if ($check && $s ne '' &&
+ (($sindent % 8) != 0 ||
+ ($sindent < $indent) ||
+ ($sindent > $indent + 8))) {
+ WARN("SUSPECT_CODE_INDENT",
+ "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n");
+ }
+ }
+
+ # Track the 'values' across context and added lines.
+ my $opline = $line; $opline =~ s/^./ /;
+ my ($curr_values, $curr_vars) =
+ annotate_values($opline . "\n", $prev_values);
+ $curr_values = $prev_values . $curr_values;
+ if ($dbg_values) {
+ my $outline = $opline; $outline =~ s/\t/ /g;
+ print "$linenr > .$outline\n";
+ print "$linenr > $curr_values\n";
+ print "$linenr > $curr_vars\n";
+ }
+ $prev_values = substr($curr_values, -1);
+
+#ignore lines not being added
+ next if ($line =~ /^[^\+]/);
+
+# check for declarations of signed or unsigned without int
+ while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) {
+ my $type = $1;
+ my $var = $2;
+ $var = "" if (!defined $var);
+ if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) {
+ my $sign = $1;
+ my $pointer = $2;
+
+ $pointer = "" if (!defined $pointer);
+
+ if (WARN("UNSPECIFIED_INT",
+ "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) &&
+ $fix) {
+ my $decl = trim($sign) . " int ";
+ my $comp_pointer = $pointer;
+ $comp_pointer =~ s/\s//g;
+ $decl .= $comp_pointer;
+ $decl = rtrim($decl) if ($var eq "");
+ $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@;
+ }
+ }
+ }
+
+# TEST: allow direct testing of the type matcher.
+ if ($dbg_type) {
+ if ($line =~ /^.\s*$Declare\s*$/) {
+ ERROR("TEST_TYPE",
+ "TEST: is type\n" . $herecurr);
+ } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) {
+ ERROR("TEST_NOT_TYPE",
+ "TEST: is not type ($1 is)\n". $herecurr);
+ }
+ next;
+ }
+# TEST: allow direct testing of the attribute matcher.
+ if ($dbg_attr) {
+ if ($line =~ /^.\s*$Modifier\s*$/) {
+ ERROR("TEST_ATTR",
+ "TEST: is attr\n" . $herecurr);
+ } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) {
+ ERROR("TEST_NOT_ATTR",
+ "TEST: is not attr ($1 is)\n". $herecurr);
+ }
+ next;
+ }
+
+# check for initialisation to aggregates open brace on the next line
+ if ($line =~ /^.\s*{/ &&
+ $prevline =~ /(?:^|[^=])=\s*$/) {
+ if (ERROR("OPEN_BRACE",
+ "that open brace { should be on the previous line\n" . $hereprev) &&
+ $fix && $prevline =~ /^\+/ && $line =~ /^\+/) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixedline = $prevrawline;
+ $fixedline =~ s/\s*=\s*$/ = {/;
+ fix_insert_line($fixlinenr, $fixedline);
+ $fixedline = $line;
+ $fixedline =~ s/^(.\s*){\s*/$1/;
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ }
+
+#
+# Checks which are anchored on the added line.
+#
+
+# check for malformed paths in #include statements (uses RAW line)
+ if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) {
+ my $path = $1;
+ if ($path =~ m{//}) {
+ ERROR("MALFORMED_INCLUDE",
+ "malformed #include filename\n" . $herecurr);
+ }
+ if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) {
+ ERROR("UAPI_INCLUDE",
+ "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr);
+ }
+ }
+
+# no C99 // comments
+ if ($line =~ m{//}) {
+ if (ERROR("C99_COMMENTS",
+ "do not use C99 // comments\n" . $herecurr) &&
+ $fix) {
+ my $line = $fixed[$fixlinenr];
+ if ($line =~ /\/\/(.*)$/) {
+ my $comment = trim($1);
+ $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@;
+ }
+ }
+ }
+ # Remove C99 comments.
+ $line =~ s@//.*@@;
+ $opline =~ s@//.*@@;
+
+# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider
+# the whole statement.
+#print "APW <$lines[$realline_next - 1]>\n";
+ if (defined $realline_next &&
+ exists $lines[$realline_next - 1] &&
+ !defined $suppress_export{$realline_next} &&
+ ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ ||
+ $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) {
+ # Handle definitions which produce identifiers with
+ # a prefix:
+ # XXX(foo);
+ # EXPORT_SYMBOL(something_foo);
+ my $name = $1;
+ if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ &&
+ $name =~ /^${Ident}_$2/) {
+#print "FOO C name<$name>\n";
+ $suppress_export{$realline_next} = 1;
+
+ } elsif ($stat !~ /(?:
+ \n.}\s*$|
+ ^.DEFINE_$Ident\(\Q$name\E\)|
+ ^.DECLARE_$Ident\(\Q$name\E\)|
+ ^.LIST_HEAD\(\Q$name\E\)|
+ ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(|
+ \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\()
+ )/x) {
+#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n";
+ $suppress_export{$realline_next} = 2;
+ } else {
+ $suppress_export{$realline_next} = 1;
+ }
+ }
+ if (!defined $suppress_export{$linenr} &&
+ $prevline =~ /^.\s*$/ &&
+ ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ ||
+ $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) {
+#print "FOO B <$lines[$linenr - 1]>\n";
+ $suppress_export{$linenr} = 2;
+ }
+ if (defined $suppress_export{$linenr} &&
+ $suppress_export{$linenr} == 2) {
+ WARN("EXPORT_SYMBOL",
+ "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr);
+ }
+
+# check for global initialisers.
+ if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/) {
+ if (ERROR("GLOBAL_INITIALISERS",
+ "do not initialise globals to $1\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/;
+ }
+ }
+# check for static initialisers.
+ if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) {
+ if (ERROR("INITIALISED_STATIC",
+ "do not initialise statics to $1\n" .
+ $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/;
+ }
+ }
+
+# check for misordered declarations of char/short/int/long with signed/unsigned
+ while ($sline =~ m{(\b$TypeMisordered\b)}g) {
+ my $tmp = trim($1);
+ WARN("MISORDERED_TYPE",
+ "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr);
+ }
+
+# check for static const char * arrays.
+ if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) {
+ WARN("STATIC_CONST_CHAR_ARRAY",
+ "static const char * array should probably be static const char * const\n" .
+ $herecurr);
+ }
+
+# check for static char foo[] = "bar" declarations.
+ if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) {
+ WARN("STATIC_CONST_CHAR_ARRAY",
+ "static char array declaration should probably be static const char\n" .
+ $herecurr);
+ }
+
+# check for const <foo> const where <foo> is not a pointer or array type
+ if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) {
+ my $found = $1;
+ if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) {
+ WARN("CONST_CONST",
+ "'const $found const *' should probably be 'const $found * const'\n" . $herecurr);
+ } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) {
+ WARN("CONST_CONST",
+ "'const $found const' should probably be 'const $found'\n" . $herecurr);
+ }
+ }
+
+# check for non-global char *foo[] = {"bar", ...} declarations.
+ if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) {
+ WARN("STATIC_CONST_CHAR_ARRAY",
+ "char * array declaration might be better as static const\n" .
+ $herecurr);
+ }
+
+# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo)
+ if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) {
+ my $array = $1;
+ if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) {
+ my $array_div = $1;
+ if (WARN("ARRAY_SIZE",
+ "Prefer ARRAY_SIZE($array)\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/;
+ }
+ }
+ }
+
+# check for function declarations without arguments like "int foo()"
+ if ($line =~ /(\b$Type\s+$Ident)\s*\(\s*\)/) {
+ if (ERROR("FUNCTION_WITHOUT_ARGS",
+ "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/;
+ }
+ }
+
+# check for new typedefs, only function parameters and sparse annotations
+# make sense.
+ if ($line =~ /\btypedef\s/ &&
+ $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ &&
+ $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ &&
+ $line !~ /\b$typeTypedefs\b/ &&
+ $line !~ /\b__bitwise(?:__|)\b/) {
+ WARN("NEW_TYPEDEFS",
+ "do not add new typedefs\n" . $herecurr);
+ }
+
+# * goes on variable not on type
+ # (char*[ const])
+ while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) {
+ #print "AA<$1>\n";
+ my ($ident, $from, $to) = ($1, $2, $2);
+
+ # Should start with a space.
+ $to =~ s/^(\S)/ $1/;
+ # Should not end with a space.
+ $to =~ s/\s+$//;
+ # '*'s should not have spaces between.
+ while ($to =~ s/\*\s+\*/\*\*/) {
+ }
+
+## print "1: from<$from> to<$to> ident<$ident>\n";
+ if ($from ne $to) {
+ if (ERROR("POINTER_LOCATION",
+ "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) &&
+ $fix) {
+ my $sub_from = $ident;
+ my $sub_to = $ident;
+ $sub_to =~ s/\Q$from\E/$to/;
+ $fixed[$fixlinenr] =~
+ s@\Q$sub_from\E@$sub_to@;
+ }
+ }
+ }
+ while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) {
+ #print "BB<$1>\n";
+ my ($match, $from, $to, $ident) = ($1, $2, $2, $3);
+
+ # Should start with a space.
+ $to =~ s/^(\S)/ $1/;
+ # Should not end with a space.
+ $to =~ s/\s+$//;
+ # '*'s should not have spaces between.
+ while ($to =~ s/\*\s+\*/\*\*/) {
+ }
+ # Modifiers should have spaces.
+ $to =~ s/(\b$Modifier$)/$1 /;
+
+## print "2: from<$from> to<$to> ident<$ident>\n";
+ if ($from ne $to && $ident !~ /^$Modifier$/) {
+ if (ERROR("POINTER_LOCATION",
+ "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) &&
+ $fix) {
+
+ my $sub_from = $match;
+ my $sub_to = $match;
+ $sub_to =~ s/\Q$from\E/$to/;
+ $fixed[$fixlinenr] =~
+ s@\Q$sub_from\E@$sub_to@;
+ }
+ }
+ }
+
+# avoid BUG() or BUG_ON()
+ if ($line =~ /\b(?:BUG|BUG_ON)\b/) {
+ my $msg_type = \&WARN;
+ $msg_type = \&CHK if ($file);
+ &{$msg_type}("AVOID_BUG",
+ "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\n" . $herecurr);
+ }
+
+# avoid LINUX_VERSION_CODE
+# if ($line =~ /\bLINUX_VERSION_CODE\b/) {
+# WARN("LINUX_VERSION_CODE",
+# "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr);
+# }
+
+# check for uses of printk_ratelimit
+ if ($line =~ /\bprintk_ratelimit\s*\(/) {
+ WARN("PRINTK_RATELIMITED",
+ "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr);
+ }
+
+# printk should use KERN_* levels. Note that follow on printk's on the
+# same line do not need a level, so we use the current block context
+# to try and find and validate the current printk. In summary the current
+# printk includes all preceding printk's which have no newline on the end.
+# we assume the first bad printk is the one to report.
+ if ($line =~ /\bprintk\((?!KERN_)\s*"/) {
+ my $ok = 0;
+ for (my $ln = $linenr - 1; $ln >= $first_line; $ln--) {
+ #print "CHECK<$lines[$ln - 1]\n";
+ # we have a preceding printk if it ends
+ # with "\n" ignore it, else it is to blame
+ if ($lines[$ln - 1] =~ m{\bprintk\(}) {
+ if ($rawlines[$ln - 1] !~ m{\\n"}) {
+ $ok = 1;
+ }
+ last;
+ }
+ }
+ if ($ok == 0) {
+ WARN("PRINTK_WITHOUT_KERN_LEVEL",
+ "printk() should include KERN_ facility level\n" . $herecurr);
+ }
+ }
+
+ if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) {
+ my $orig = $1;
+ my $level = lc($orig);
+ $level = "warn" if ($level eq "warning");
+ my $level2 = $level;
+ $level2 = "dbg" if ($level eq "debug");
+ WARN("PREFER_PR_LEVEL",
+ "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to printk(KERN_$orig ...\n" . $herecurr);
+ }
+
+ if ($line =~ /\bpr_warning\s*\(/) {
+ if (WARN("PREFER_PR_LEVEL",
+ "Prefer pr_warn(... to pr_warning(...\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\bpr_warning\b/pr_warn/;
+ }
+ }
+
+ if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) {
+ my $orig = $1;
+ my $level = lc($orig);
+ $level = "warn" if ($level eq "warning");
+ $level = "dbg" if ($level eq "debug");
+ WARN("PREFER_DEV_LEVEL",
+ "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr);
+ }
+
+# ENOSYS means "bad syscall nr" and nothing else. This will have a small
+# number of false positives, but assembly files are not checked, so at
+# least the arch entry code will not trigger this warning.
+ if ($line =~ /\bENOSYS\b/) {
+ WARN("ENOSYS",
+ "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr);
+ }
+
+# function brace can't be on same line, except for #defines of do while,
+# or if closed on same line
+ if (($line=~/$Type\s*$Ident\(.*\).*\s*{/) and
+ !($line=~/\#\s*define.*do\s\{/) and !($line=~/}/)) {
+ if (ERROR("OPEN_BRACE",
+ "open brace '{' following function declarations go on the next line\n" . $herecurr) &&
+ $fix) {
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixed_line = $rawline;
+ $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*){(.*)$/;
+ my $line1 = $1;
+ my $line2 = $2;
+ fix_insert_line($fixlinenr, ltrim($line1));
+ fix_insert_line($fixlinenr, "\+{");
+ if ($line2 !~ /^\s*$/) {
+ fix_insert_line($fixlinenr, "\+\t" . trim($line2));
+ }
+ }
+ }
+
+# open braces for enum, union and struct go on the same line.
+ if ($line =~ /^.\s*{/ &&
+ $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) {
+ if (ERROR("OPEN_BRACE",
+ "open brace '{' following $1 go on the same line\n" . $hereprev) &&
+ $fix && $prevline =~ /^\+/ && $line =~ /^\+/) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixedline = rtrim($prevrawline) . " {";
+ fix_insert_line($fixlinenr, $fixedline);
+ $fixedline = $rawline;
+ $fixedline =~ s/^(.\s*){\s*/$1\t/;
+ if ($fixedline !~ /^\+\s*$/) {
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ }
+ }
+
+# missing space after union, struct or enum definition
+ if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) {
+ if (WARN("SPACING",
+ "missing space after $1 definition\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/;
+ }
+ }
+
+# Function pointer declarations
+# check spacing between type, funcptr, and args
+# canonical declaration is "type (*funcptr)(args...)"
+ if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) {
+ my $declare = $1;
+ my $pre_pointer_space = $2;
+ my $post_pointer_space = $3;
+ my $funcname = $4;
+ my $post_funcname_space = $5;
+ my $pre_args_space = $6;
+
+# the $Declare variable will capture all spaces after the type
+# so check it for a missing trailing missing space but pointer return types
+# don't need a space so don't warn for those.
+ my $post_declare_space = "";
+ if ($declare =~ /(\s+)$/) {
+ $post_declare_space = $1;
+ $declare = rtrim($declare);
+ }
+ if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) {
+ WARN("SPACING",
+ "missing space after return type\n" . $herecurr);
+ $post_declare_space = " ";
+ }
+
+# unnecessary space "type (*funcptr)(args...)"
+# This test is not currently implemented because these declarations are
+# equivalent to
+# int foo(int bar, ...)
+# and this is form shouldn't/doesn't generate a checkpatch warning.
+#
+# elsif ($declare =~ /\s{2,}$/) {
+# WARN("SPACING",
+# "Multiple spaces after return type\n" . $herecurr);
+# }
+
+# unnecessary space "type ( *funcptr)(args...)"
+ if (defined $pre_pointer_space &&
+ $pre_pointer_space =~ /^\s/) {
+ WARN("SPACING",
+ "Unnecessary space after function pointer open parenthesis\n" . $herecurr);
+ }
+
+# unnecessary space "type (* funcptr)(args...)"
+ if (defined $post_pointer_space &&
+ $post_pointer_space =~ /^\s/) {
+ WARN("SPACING",
+ "Unnecessary space before function pointer name\n" . $herecurr);
+ }
+
+# unnecessary space "type (*funcptr )(args...)"
+ if (defined $post_funcname_space &&
+ $post_funcname_space =~ /^\s/) {
+ WARN("SPACING",
+ "Unnecessary space after function pointer name\n" . $herecurr);
+ }
+
+# unnecessary space "type (*funcptr) (args...)"
+ if (defined $pre_args_space &&
+ $pre_args_space =~ /^\s/) {
+ WARN("SPACING",
+ "Unnecessary space before function pointer arguments\n" . $herecurr);
+ }
+
+ if (show_type("SPACING") && $fix) {
+ $fixed[$fixlinenr] =~
+ s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex;
+ }
+ }
+
+# check for spacing round square brackets; allowed:
+# 1. with a type on the left -- int [] a;
+# 2. at the beginning of a line for slice initialisers -- [0...10] = 5,
+# 3. inside a curly brace -- = { [0...10] = 5 }
+ while ($line =~ /(.*?\s)\[/g) {
+ my ($where, $prefix) = ($-[1], $1);
+ if ($prefix !~ /$Type\s+$/ &&
+ ($where != 0 || $prefix !~ /^.\s+$/) &&
+ $prefix !~ /[{,]\s+$/) {
+ if (ERROR("BRACKET_SPACE",
+ "space prohibited before open square bracket '['\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/^(\+.*?)\s+\[/$1\[/;
+ }
+ }
+ }
+
+# check for spaces between functions and their parentheses.
+ while ($line =~ /($Ident)\s+\(/g) {
+ my $name = $1;
+ my $ctx_before = substr($line, 0, $-[1]);
+ my $ctx = "$ctx_before$name";
+
+ # Ignore those directives where spaces _are_ permitted.
+ if ($name =~ /^(?:
+ if|for|while|switch|return|case|
+ volatile|__volatile__|
+ __attribute__|format|__extension__|
+ asm|__asm__)$/x)
+ {
+ # cpp #define statements have non-optional spaces, ie
+ # if there is a space between the name and the open
+ # parenthesis it is simply not a parameter group.
+ } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) {
+
+ # cpp #elif statement condition may start with a (
+ } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) {
+
+ # If this whole things ends with a type its most
+ # likely a typedef for a function.
+ } elsif ($ctx =~ /$Type$/) {
+
+ } else {
+ if (WARN("SPACING",
+ "space prohibited between function name and open parenthesis '('\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\b$name\s+\(/$name\(/;
+ }
+ }
+ }
+
+# Check operator spacing.
+ if (!($line=~/\#\s*include/)) {
+ my $fixed_line = "";
+ my $line_fixed = 0;
+
+ my $ops = qr{
+ <<=|>>=|<=|>=|==|!=|
+ \+=|-=|\*=|\/=|%=|\^=|\|=|&=|
+ =>|->|<<|>>|<|>|=|!|~|
+ &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%|
+ \?:|\?|:
+ }x;
+ my @elements = split(/($ops|;)/, $opline);
+
+## print("element count: <" . $#elements . ">\n");
+## foreach my $el (@elements) {
+## print("el: <$el>\n");
+## }
+
+ my @fix_elements = ();
+ my $off = 0;
+
+ foreach my $el (@elements) {
+ push(@fix_elements, substr($rawline, $off, length($el)));
+ $off += length($el);
+ }
+
+ $off = 0;
+
+ my $blank = copy_spacing($opline);
+ my $last_after = -1;
+
+ for (my $n = 0; $n < $#elements; $n += 2) {
+
+ my $good = $fix_elements[$n] . $fix_elements[$n + 1];
+
+## print("n: <$n> good: <$good>\n");
+
+ $off += length($elements[$n]);
+
+ # Pick up the preceding and succeeding characters.
+ my $ca = substr($opline, 0, $off);
+ my $cc = '';
+ if (length($opline) >= ($off + length($elements[$n + 1]))) {
+ $cc = substr($opline, $off + length($elements[$n + 1]));
+ }
+ my $cb = "$ca$;$cc";
+
+ my $a = '';
+ $a = 'V' if ($elements[$n] ne '');
+ $a = 'W' if ($elements[$n] =~ /\s$/);
+ $a = 'C' if ($elements[$n] =~ /$;$/);
+ $a = 'B' if ($elements[$n] =~ /(\[|\()$/);
+ $a = 'O' if ($elements[$n] eq '');
+ $a = 'E' if ($ca =~ /^\s*$/);
+
+ my $op = $elements[$n + 1];
+
+ my $c = '';
+ if (defined $elements[$n + 2]) {
+ $c = 'V' if ($elements[$n + 2] ne '');
+ $c = 'W' if ($elements[$n + 2] =~ /^\s/);
+ $c = 'C' if ($elements[$n + 2] =~ /^$;/);
+ $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/);
+ $c = 'O' if ($elements[$n + 2] eq '');
+ $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/);
+ } else {
+ $c = 'E';
+ }
+
+ my $ctx = "${a}x${c}";
+
+ my $at = "(ctx:$ctx)";
+
+ my $ptr = substr($blank, 0, $off) . "^";
+ my $hereptr = "$hereline$ptr\n";
+
+ # Pull out the value of this operator.
+ my $op_type = substr($curr_values, $off + 1, 1);
+
+ # Get the full operator variant.
+ my $opv = $op . substr($curr_vars, $off, 1);
+
+ # Ignore operators passed as parameters.
+ if ($op_type ne 'V' &&
+ $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) {
+
+# # Ignore comments
+# } elsif ($op =~ /^$;+$/) {
+
+ # ; should have either the end of line or a space or \ after it
+ } elsif ($op eq ';') {
+ if ($ctx !~ /.x[WEBC]/ &&
+ $cc !~ /^\\/ && $cc !~ /^;/) {
+ if (ERROR("SPACING",
+ "space required after that '$op' $at\n" . $hereptr)) {
+ $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " ";
+ $line_fixed = 1;
+ }
+ }
+
+ # // is a comment
+ } elsif ($op eq '//') {
+
+ # : when part of a bitfield
+ } elsif ($opv eq ':B') {
+ # skip the bitfield test for now
+
+ # No spaces for:
+ # ->
+ } elsif ($op eq '->') {
+ if ($ctx =~ /Wx.|.xW/) {
+ if (ERROR("SPACING",
+ "spaces prohibited around that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]);
+ if (defined $fix_elements[$n + 2]) {
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ }
+ $line_fixed = 1;
+ }
+ }
+
+ # , must not have a space before and must have a space on the right.
+ } elsif ($op eq ',') {
+ my $rtrim_before = 0;
+ my $space_after = 0;
+ if ($ctx =~ /Wx./) {
+ if (ERROR("SPACING",
+ "space prohibited before that '$op' $at\n" . $hereptr)) {
+ $line_fixed = 1;
+ $rtrim_before = 1;
+ }
+ }
+ if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) {
+ if (ERROR("SPACING",
+ "space required after that '$op' $at\n" . $hereptr)) {
+ $line_fixed = 1;
+ $last_after = $n;
+ $space_after = 1;
+ }
+ }
+ if ($rtrim_before || $space_after) {
+ if ($rtrim_before) {
+ $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]);
+ } else {
+ $good = $fix_elements[$n] . trim($fix_elements[$n + 1]);
+ }
+ if ($space_after) {
+ $good .= " ";
+ }
+ }
+
+ # '*' as part of a type definition -- reported already.
+ } elsif ($opv eq '*_') {
+ #warn "'*' is part of type\n";
+
+ # unary operators should have a space before and
+ # none after. May be left adjacent to another
+ # unary operator, or a cast
+ } elsif ($op eq '!' || $op eq '~' ||
+ $opv eq '*U' || $opv eq '-U' ||
+ $opv eq '&U' || $opv eq '&&U') {
+ if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) {
+ if (ERROR("SPACING",
+ "space required before that '$op' $at\n" . $hereptr)) {
+ if ($n != $last_after + 2) {
+ $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]);
+ $line_fixed = 1;
+ }
+ }
+ }
+ if ($op eq '*' && $cc =~/\s*$Modifier\b/) {
+ # A unary '*' may be const
+
+ } elsif ($ctx =~ /.xW/) {
+ if (ERROR("SPACING",
+ "space prohibited after that '$op' $at\n" . $hereptr)) {
+ $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]);
+ if (defined $fix_elements[$n + 2]) {
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ }
+ $line_fixed = 1;
+ }
+ }
+
+ # unary ++ and unary -- are allowed no space on one side.
+ } elsif ($op eq '++' or $op eq '--') {
+ if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) {
+ if (ERROR("SPACING",
+ "space required one side of that '$op' $at\n" . $hereptr)) {
+ $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " ";
+ $line_fixed = 1;
+ }
+ }
+ if ($ctx =~ /Wx[BE]/ ||
+ ($ctx =~ /Wx./ && $cc =~ /^;/)) {
+ if (ERROR("SPACING",
+ "space prohibited before that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]);
+ $line_fixed = 1;
+ }
+ }
+ if ($ctx =~ /ExW/) {
+ if (ERROR("SPACING",
+ "space prohibited after that '$op' $at\n" . $hereptr)) {
+ $good = $fix_elements[$n] . trim($fix_elements[$n + 1]);
+ if (defined $fix_elements[$n + 2]) {
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ }
+ $line_fixed = 1;
+ }
+ }
+
+ # << and >> may either have or not have spaces both sides
+ } elsif ($op eq '<<' or $op eq '>>' or
+ $op eq '&' or $op eq '^' or $op eq '|' or
+ $op eq '+' or $op eq '-' or
+ $op eq '*' or $op eq '/' or
+ $op eq '%')
+ {
+ if ($check) {
+ if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) {
+ if (CHK("SPACING",
+ "spaces preferred around that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " ";
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ $line_fixed = 1;
+ }
+ } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) {
+ if (CHK("SPACING",
+ "space preferred before that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]);
+ $line_fixed = 1;
+ }
+ }
+ } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) {
+ if (ERROR("SPACING",
+ "need consistent spacing around '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " ";
+ if (defined $fix_elements[$n + 2]) {
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ }
+ $line_fixed = 1;
+ }
+ }
+
+ # A colon needs no spaces before when it is
+ # terminating a case value or a label.
+ } elsif ($opv eq ':C' || $opv eq ':L') {
+ if ($ctx =~ /Wx./) {
+ if (ERROR("SPACING",
+ "space prohibited before that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]);
+ $line_fixed = 1;
+ }
+ }
+
+ # All the others need spaces both sides.
+ } elsif ($ctx !~ /[EWC]x[CWE]/) {
+ my $ok = 0;
+
+ # Ignore email addresses <foo@bar>
+ if (($op eq '<' &&
+ $cc =~ /^\S+\@\S+>/) ||
+ ($op eq '>' &&
+ $ca =~ /<\S+\@\S+$/))
+ {
+ $ok = 1;
+ }
+
+ # for asm volatile statements
+ # ignore a colon with another
+ # colon immediately before or after
+ if (($op eq ':') &&
+ ($ca =~ /:$/ || $cc =~ /^:/)) {
+ $ok = 1;
+ }
+
+ # messages are ERROR, but ?: are CHK
+ if ($ok == 0) {
+ my $msg_type = \&ERROR;
+ $msg_type = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/);
+
+ if (&{$msg_type}("SPACING",
+ "spaces required around that '$op' $at\n" . $hereptr)) {
+ $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " ";
+ if (defined $fix_elements[$n + 2]) {
+ $fix_elements[$n + 2] =~ s/^\s+//;
+ }
+ $line_fixed = 1;
+ }
+ }
+ }
+ $off += length($elements[$n + 1]);
+
+## print("n: <$n> GOOD: <$good>\n");
+
+ $fixed_line = $fixed_line . $good;
+ }
+
+ if (($#elements % 2) == 0) {
+ $fixed_line = $fixed_line . $fix_elements[$#elements];
+ }
+
+ if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) {
+ $fixed[$fixlinenr] = $fixed_line;
+ }
+
+
+ }
+
+# check for whitespace before a non-naked semicolon
+ if ($line =~ /^\+.*\S\s+;\s*$/) {
+ if (WARN("SPACING",
+ "space prohibited before semicolon\n" . $herecurr) &&
+ $fix) {
+ 1 while $fixed[$fixlinenr] =~
+ s/^(\+.*\S)\s+;/$1;/;
+ }
+ }
+
+# check for multiple assignments
+ if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) {
+ CHK("MULTIPLE_ASSIGNMENTS",
+ "multiple assignments should be avoided\n" . $herecurr);
+ }
+
+## # check for multiple declarations, allowing for a function declaration
+## # continuation.
+## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ &&
+## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) {
+##
+## # Remove any bracketed sections to ensure we do not
+## # falsly report the parameters of functions.
+## my $ln = $line;
+## while ($ln =~ s/\([^\(\)]*\)//g) {
+## }
+## if ($ln =~ /,/) {
+## WARN("MULTIPLE_DECLARATION",
+## "declaring multiple variables together should be avoided\n" . $herecurr);
+## }
+## }
+
+#need space before brace following if, while, etc
+ if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) ||
+ $line =~ /do\{/) {
+ if (ERROR("SPACING",
+ "space required before the open brace '{'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/^(\+.*(?:do|\))){/$1 {/;
+ }
+ }
+
+## # check for blank lines before declarations
+## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ &&
+## $prevrawline =~ /^.\s*$/) {
+## WARN("SPACING",
+## "No blank lines before declarations\n" . $hereprev);
+## }
+##
+
+# closing brace should have a space following it when it has anything
+# on the line
+ if ($line =~ /}(?!(?:,|;|\)))\S/) {
+ if (ERROR("SPACING",
+ "space required after that close brace '}'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/}((?!(?:,|;|\)))\S)/} $1/;
+ }
+ }
+
+# check spacing on square brackets
+ if ($line =~ /\[\s/ && $line !~ /\[\s*$/) {
+ if (ERROR("SPACING",
+ "space prohibited after that open square bracket '['\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\[\s+/\[/;
+ }
+ }
+ if ($line =~ /\s\]/) {
+ if (ERROR("SPACING",
+ "space prohibited before that close square bracket ']'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\s+\]/\]/;
+ }
+ }
+
+# check spacing on parentheses
+ if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ &&
+ $line !~ /for\s*\(\s+;/) {
+ if (ERROR("SPACING",
+ "space prohibited after that open parenthesis '('\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\(\s+/\(/;
+ }
+ }
+ if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ &&
+ $line !~ /for\s*\(.*;\s+\)/ &&
+ $line !~ /:\s+\)/) {
+ if (ERROR("SPACING",
+ "space prohibited before that close parenthesis ')'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\s+\)/\)/;
+ }
+ }
+
+# check unnecessary parentheses around addressof/dereference single $Lvals
+# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar
+
+ while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) {
+ my $var = $1;
+ if (CHK("UNNECESSARY_PARENTHESES",
+ "Unnecessary parentheses around $var\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/;
+ }
+ }
+
+# check for unnecessary parentheses around function pointer uses
+# ie: (foo->bar)(); should be foo->bar();
+# but not "if (foo->bar) (" to avoid some false positives
+ if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) {
+ my $var = $2;
+ if (CHK("UNNECESSARY_PARENTHESES",
+ "Unnecessary parentheses around function pointer $var\n" . $herecurr) &&
+ $fix) {
+ my $var2 = deparenthesize($var);
+ $var2 =~ s/\s//g;
+ $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/;
+ }
+ }
+
+#goto labels aren't indented, allow a single space however
+ if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and
+ !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) {
+ if (WARN("INDENTED_LABEL",
+ "labels should not be indented\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/^(.)\s+/$1/;
+ }
+ }
+
+# return is not a function
+ if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) {
+ my $spacing = $1;
+ if ($^V && $^V ge 5.10.0 &&
+ $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) {
+ my $value = $1;
+ $value = deparenthesize($value);
+ if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) {
+ ERROR("RETURN_PARENTHESES",
+ "return is not a function, parentheses are not required\n" . $herecurr);
+ }
+ } elsif ($spacing !~ /\s+/) {
+ ERROR("SPACING",
+ "space required before the open parenthesis '('\n" . $herecurr);
+ }
+ }
+
+# unnecessary return in a void function
+# at end-of-function, with the previous line a single leading tab, then return;
+# and the line before that not a goto label target like "out:"
+ if ($sline =~ /^[ \+]}\s*$/ &&
+ $prevline =~ /^\+\treturn\s*;\s*$/ &&
+ $linenr >= 3 &&
+ $lines[$linenr - 3] =~ /^[ +]/ &&
+ $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) {
+ WARN("RETURN_VOID",
+ "void function return statements are not generally useful\n" . $hereprev);
+ }
+
+# if statements using unnecessary parentheses - ie: if ((foo == bar))
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\bif\s*((?:\(\s*){2,})/) {
+ my $openparens = $1;
+ my $count = $openparens =~ tr@\(@\(@;
+ my $msg = "";
+ if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) {
+ my $comp = $4; #Not $1 because of $LvalOrFunc
+ $msg = " - maybe == should be = ?" if ($comp eq "==");
+ WARN("UNNECESSARY_PARENTHESES",
+ "Unnecessary parentheses$msg\n" . $herecurr);
+ }
+ }
+
+# comparisons with a constant or upper case identifier on the left
+# avoid cases like "foo + BAR < baz"
+# only fix matches surrounded by parentheses to avoid incorrect
+# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5"
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) {
+ my $lead = $1;
+ my $const = $2;
+ my $comp = $3;
+ my $to = $4;
+ my $newcomp = $comp;
+ if ($lead !~ /(?:$Operators|\.)\s*$/ &&
+ $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ &&
+ WARN("CONSTANT_COMPARISON",
+ "Comparisons should place the constant on the right side of the test\n" . $herecurr) &&
+ $fix) {
+ if ($comp eq "<") {
+ $newcomp = ">";
+ } elsif ($comp eq "<=") {
+ $newcomp = ">=";
+ } elsif ($comp eq ">") {
+ $newcomp = "<";
+ } elsif ($comp eq ">=") {
+ $newcomp = "<=";
+ }
+ $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/;
+ }
+ }
+
+# Return of what appears to be an errno should normally be negative
+ if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) {
+ my $name = $1;
+ if ($name ne 'EOF' && $name ne 'ERROR') {
+ WARN("USE_NEGATIVE_ERRNO",
+ "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr);
+ }
+ }
+
+# Need a space before open parenthesis after if, while etc
+ if ($line =~ /\b(if|while|for|switch)\(/) {
+ if (ERROR("SPACING",
+ "space required before the open parenthesis '('\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\b(if|while|for|switch)\(/$1 \(/;
+ }
+ }
+
+# Check for illegal assignment in if conditional -- and check for trailing
+# statements after the conditional.
+ if ($line =~ /do\s*(?!{)/) {
+ ($stat, $cond, $line_nr_next, $remain_next, $off_next) =
+ ctx_statement_block($linenr, $realcnt, 0)
+ if (!defined $stat);
+ my ($stat_next) = ctx_statement_block($line_nr_next,
+ $remain_next, $off_next);
+ $stat_next =~ s/\n./\n /g;
+ ##print "stat<$stat> stat_next<$stat_next>\n";
+
+ if ($stat_next =~ /^\s*while\b/) {
+ # If the statement carries leading newlines,
+ # then count those as offsets.
+ my ($whitespace) =
+ ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s);
+ my $offset =
+ statement_rawlines($whitespace) - 1;
+
+ $suppress_whiletrailers{$line_nr_next +
+ $offset} = 1;
+ }
+ }
+ if (!defined $suppress_whiletrailers{$linenr} &&
+ defined($stat) && defined($cond) &&
+ $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) {
+ my ($s, $c) = ($stat, $cond);
+
+ if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) {
+ ERROR("ASSIGN_IN_IF",
+ "do not use assignment in if condition\n" . $herecurr);
+ }
+
+ # Find out what is on the end of the line after the
+ # conditional.
+ substr($s, 0, length($c), '');
+ $s =~ s/\n.*//g;
+ $s =~ s/$;//g; # Remove any comments
+ if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ &&
+ $c !~ /}\s*while\s*/)
+ {
+ # Find out how long the conditional actually is.
+ my @newlines = ($c =~ /\n/gs);
+ my $cond_lines = 1 + $#newlines;
+ my $stat_real = '';
+
+ $stat_real = raw_line($linenr, $cond_lines)
+ . "\n" if ($cond_lines);
+ if (defined($stat_real) && $cond_lines > 1) {
+ $stat_real = "[...]\n$stat_real";
+ }
+
+ ERROR("TRAILING_STATEMENTS",
+ "trailing statements should be on next line\n" . $herecurr . $stat_real);
+ }
+ }
+
+# Check for bitwise tests written as boolean
+ if ($line =~ /
+ (?:
+ (?:\[|\(|\&\&|\|\|)
+ \s*0[xX][0-9]+\s*
+ (?:\&\&|\|\|)
+ |
+ (?:\&\&|\|\|)
+ \s*0[xX][0-9]+\s*
+ (?:\&\&|\|\||\)|\])
+ )/x)
+ {
+ WARN("HEXADECIMAL_BOOLEAN_TEST",
+ "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr);
+ }
+
+# if and else should not have general statements after it
+ if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) {
+ my $s = $1;
+ $s =~ s/$;//g; # Remove any comments
+ if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) {
+ ERROR("TRAILING_STATEMENTS",
+ "trailing statements should be on next line\n" . $herecurr);
+ }
+ }
+# if should not continue a brace
+ if ($line =~ /}\s*if\b/) {
+ ERROR("TRAILING_STATEMENTS",
+ "trailing statements should be on next line (or did you mean 'else if'?)\n" .
+ $herecurr);
+ }
+# case and default should not have general statements after them
+ if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g &&
+ $line !~ /\G(?:
+ (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$|
+ \s*return\s+
+ )/xg)
+ {
+ ERROR("TRAILING_STATEMENTS",
+ "trailing statements should be on next line\n" . $herecurr);
+ }
+
+ # Check for }<nl>else {, these must be at the same
+ # indent level to be relevant to each other.
+ if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ &&
+ $previndent == $indent) {
+ if (ERROR("ELSE_AFTER_BRACE",
+ "else should follow close brace '}'\n" . $hereprev) &&
+ $fix && $prevline =~ /^\+/ && $line =~ /^\+/) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixedline = $prevrawline;
+ $fixedline =~ s/}\s*$//;
+ if ($fixedline !~ /^\+\s*$/) {
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ $fixedline = $rawline;
+ $fixedline =~ s/^(.\s*)else/$1} else/;
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ }
+
+ if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ &&
+ $previndent == $indent) {
+ my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0);
+
+ # Find out what is on the end of the line after the
+ # conditional.
+ substr($s, 0, length($c), '');
+ $s =~ s/\n.*//g;
+
+ if ($s =~ /^\s*;/) {
+ if (ERROR("WHILE_AFTER_BRACE",
+ "while should follow close brace '}'\n" . $hereprev) &&
+ $fix && $prevline =~ /^\+/ && $line =~ /^\+/) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixedline = $prevrawline;
+ my $trailing = $rawline;
+ $trailing =~ s/^\+//;
+ $trailing = trim($trailing);
+ $fixedline =~ s/}\s*$/} $trailing/;
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ }
+ }
+
+#Specific variable tests
+ while ($line =~ m{($Constant|$Lval)}g) {
+ my $var = $1;
+
+#gcc binary extension
+ if ($var =~ /^$Binary$/) {
+ if (WARN("GCC_BINARY_CONSTANT",
+ "Avoid gcc v4.3+ binary constant extension: <$var>\n" . $herecurr) &&
+ $fix) {
+ my $hexval = sprintf("0x%x", oct($var));
+ $fixed[$fixlinenr] =~
+ s/\b$var\b/$hexval/;
+ }
+ }
+
+#CamelCase
+ if ($var !~ /^$Constant$/ &&
+ $var =~ /[A-Z][a-z]|[a-z][A-Z]/ &&
+#Ignore Page<foo> variants
+ $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ &&
+#Ignore SI style variants like nS, mV and dB (ie: max_uV, regulator_min_uA_show)
+ $var !~ /^(?:[a-z_]*?)_?[a-z][A-Z](?:_[a-z_]+)?$/ &&
+#Ignore some three character SI units explicitly, like MiB and KHz
+ $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) {
+ while ($var =~ m{($Ident)}g) {
+ my $word = $1;
+ next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/);
+ if ($check) {
+ seed_camelcase_includes();
+ if (!$file && !$camelcase_file_seeded) {
+ seed_camelcase_file($realfile);
+ $camelcase_file_seeded = 1;
+ }
+ }
+ if (!defined $camelcase{$word}) {
+ $camelcase{$word} = 1;
+ CHK("CAMELCASE",
+ "Avoid CamelCase: <$word>\n" . $herecurr);
+ }
+ }
+ }
+ }
+
+#no spaces allowed after \ in define
+ if ($line =~ /\#\s*define.*\\\s+$/) {
+ if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION",
+ "Whitespace after \\ makes next lines useless\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\s+$//;
+ }
+ }
+
+# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes
+# itself <asm/foo.h> (uses RAW line)
+ if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) {
+ my $file = "$1.h";
+ my $checkfile = "include/linux/$file";
+ if (-f "$root/$checkfile" &&
+ $realfile ne $checkfile &&
+ $1 !~ /$allowed_asm_includes/)
+ {
+ my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`;
+ if ($asminclude > 0) {
+ if ($realfile =~ m{^arch/}) {
+ CHK("ARCH_INCLUDE_LINUX",
+ "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr);
+ } else {
+ WARN("INCLUDE_LINUX",
+ "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr);
+ }
+ }
+ }
+ }
+
+# multi-statement macros should be enclosed in a do while loop, grab the
+# first statement and ensure its the whole macro if its not enclosed
+# in a known good container
+ if ($realfile !~ m@/vmlinux.lds.h$@ &&
+ $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) {
+ my $ln = $linenr;
+ my $cnt = $realcnt;
+ my ($off, $dstat, $dcond, $rest);
+ my $ctx = '';
+ my $has_flow_statement = 0;
+ my $has_arg_concat = 0;
+ ($dstat, $dcond, $ln, $cnt, $off) =
+ ctx_statement_block($linenr, $realcnt, 0);
+ $ctx = $dstat;
+ #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n";
+ #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n";
+
+ $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/);
+ $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/);
+
+ $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//;
+ my $define_args = $1;
+ my $define_stmt = $dstat;
+ my @def_args = ();
+
+ if (defined $define_args && $define_args ne "") {
+ $define_args = substr($define_args, 1, length($define_args) - 2);
+ $define_args =~ s/\s*//g;
+ @def_args = split(",", $define_args);
+ }
+
+ $dstat =~ s/$;//g;
+ $dstat =~ s/\\\n.//g;
+ $dstat =~ s/^\s*//s;
+ $dstat =~ s/\s*$//s;
+
+ # Flatten any parentheses and braces
+ while ($dstat =~ s/\([^\(\)]*\)/1/ ||
+ $dstat =~ s/\{[^\{\}]*\}/1/ ||
+ $dstat =~ s/.\[[^\[\]]*\]/1/)
+ {
+ }
+
+ # Flatten any obvious string concatentation.
+ while ($dstat =~ s/($String)\s*$Ident/$1/ ||
+ $dstat =~ s/$Ident\s*($String)/$1/)
+ {
+ }
+
+ # Make asm volatile uses seem like a generic function
+ $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g;
+
+ my $exceptions = qr{
+ $Declare|
+ module_param_named|
+ MODULE_PARM_DESC|
+ DECLARE_PER_CPU|
+ DEFINE_PER_CPU|
+ __typeof__\(|
+ union|
+ struct|
+ \.$Ident\s*=\s*|
+ ^\"|\"$|
+ ^\[
+ }x;
+ #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n";
+
+ $ctx =~ s/\n*$//;
+ my $herectx = $here . "\n";
+ my $stmt_cnt = statement_rawlines($ctx);
+
+ for (my $n = 0; $n < $stmt_cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ if ($dstat ne '' &&
+ $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(),
+ $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo();
+ $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz
+ $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants
+ $dstat !~ /$exceptions/ &&
+ $dstat !~ /^\.$Ident\s*=/ && # .foo =
+ $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo
+ $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...)
+ $dstat !~ /^for\s*$Constant$/ && # for (...)
+ $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar()
+ $dstat !~ /^do\s*{/ && # do {...
+ $dstat !~ /^\(\{/ && # ({...
+ $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/)
+ {
+
+ if ($dstat =~ /;/) {
+ ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE",
+ "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx");
+ } else {
+ ERROR("COMPLEX_MACRO",
+ "Macros with complex values should be enclosed in parentheses\n" . "$herectx");
+ }
+
+ }
+
+ # Make $define_stmt single line, comment-free, etc
+ my @stmt_array = split('\n', $define_stmt);
+ my $first = 1;
+ $define_stmt = "";
+ foreach my $l (@stmt_array) {
+ $l =~ s/\\$//;
+ if ($first) {
+ $define_stmt = $l;
+ $first = 0;
+ } elsif ($l =~ /^[\+ ]/) {
+ $define_stmt .= substr($l, 1);
+ }
+ }
+ $define_stmt =~ s/$;//g;
+ $define_stmt =~ s/\s+/ /g;
+ $define_stmt = trim($define_stmt);
+
+# check if any macro arguments are reused (ignore '...' and 'type')
+ foreach my $arg (@def_args) {
+ next if ($arg =~ /\.\.\./);
+ next if ($arg =~ /^type$/i);
+ my $tmp = $define_stmt;
+ $tmp =~ s/\b(typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g;
+ $tmp =~ s/\#+\s*$arg\b//g;
+ $tmp =~ s/\b$arg\s*\#\#//g;
+ my $use_cnt = $tmp =~ s/\b$arg\b//g;
+ if ($use_cnt > 1) {
+ CHK("MACRO_ARG_REUSE",
+ "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx");
+ }
+# check if any macro arguments may have other precedence issues
+ if ($define_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m &&
+ ((defined($1) && $1 ne ',') ||
+ (defined($2) && $2 ne ','))) {
+ CHK("MACRO_ARG_PRECEDENCE",
+ "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx");
+ }
+ }
+
+# check for macros with flow control, but without ## concatenation
+# ## concatenation is commonly a macro that defines a function so ignore those
+ if ($has_flow_statement && !$has_arg_concat) {
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($ctx);
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+ WARN("MACRO_WITH_FLOW_CONTROL",
+ "Macros with flow control statements should be avoided\n" . "$herectx");
+ }
+
+# check for line continuations outside of #defines, preprocessor #, and asm
+
+ } else {
+ if ($prevline !~ /^..*\\$/ &&
+ $line !~ /^\+\s*\#.*\\$/ && # preprocessor
+ $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm
+ $line =~ /^\+.*\\$/) {
+ WARN("LINE_CONTINUATIONS",
+ "Avoid unnecessary line continuations\n" . $herecurr);
+ }
+ }
+
+# do {} while (0) macro tests:
+# single-statement macros do not need to be enclosed in do while (0) loop,
+# macro should not end with a semicolon
+ if ($^V && $^V ge 5.10.0 &&
+ $realfile !~ m@/vmlinux.lds.h$@ &&
+ $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) {
+ my $ln = $linenr;
+ my $cnt = $realcnt;
+ my ($off, $dstat, $dcond, $rest);
+ my $ctx = '';
+ ($dstat, $dcond, $ln, $cnt, $off) =
+ ctx_statement_block($linenr, $realcnt, 0);
+ $ctx = $dstat;
+
+ $dstat =~ s/\\\n.//g;
+ $dstat =~ s/$;/ /g;
+
+ if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) {
+ my $stmts = $2;
+ my $semis = $3;
+
+ $ctx =~ s/\n*$//;
+ my $cnt = statement_rawlines($ctx);
+ my $herectx = $here . "\n";
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ if (($stmts =~ tr/;/;/) == 1 &&
+ $stmts !~ /^\s*(if|while|for|switch)\b/) {
+ WARN("SINGLE_STATEMENT_DO_WHILE_MACRO",
+ "Single statement macros should not use a do {} while (0) loop\n" . "$herectx");
+ }
+ if (defined $semis && $semis ne "") {
+ WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON",
+ "do {} while (0) macros should not be semicolon terminated\n" . "$herectx");
+ }
+ } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) {
+ $ctx =~ s/\n*$//;
+ my $cnt = statement_rawlines($ctx);
+ my $herectx = $here . "\n";
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ WARN("TRAILING_SEMICOLON",
+ "macros should not use a trailing semicolon\n" . "$herectx");
+ }
+ }
+
+# make sure symbols are always wrapped with VMLINUX_SYMBOL() ...
+# all assignments may have only one of the following with an assignment:
+# .
+# ALIGN(...)
+# VMLINUX_SYMBOL(...)
+ if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) {
+ WARN("MISSING_VMLINUX_SYMBOL",
+ "vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr);
+ }
+
+# check for redundant bracing round if etc
+ if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) {
+ my ($level, $endln, @chunks) =
+ ctx_statement_full($linenr, $realcnt, 1);
+ #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n";
+ #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n";
+ if ($#chunks > 0 && $level == 0) {
+ my @allowed = ();
+ my $allow = 0;
+ my $seen = 0;
+ my $herectx = $here . "\n";
+ my $ln = $linenr - 1;
+ for my $chunk (@chunks) {
+ my ($cond, $block) = @{$chunk};
+
+ # If the condition carries leading newlines, then count those as offsets.
+ my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s);
+ my $offset = statement_rawlines($whitespace) - 1;
+
+ $allowed[$allow] = 0;
+ #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n";
+
+ # We have looked at and allowed this specific line.
+ $suppress_ifbraces{$ln + $offset} = 1;
+
+ $herectx .= "$rawlines[$ln + $offset]\n[...]\n";
+ $ln += statement_rawlines($block) - 1;
+
+ substr($block, 0, length($cond), '');
+
+ $seen++ if ($block =~ /^\s*{/);
+
+ #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n";
+ if (statement_lines($cond) > 1) {
+ #print "APW: ALLOWED: cond<$cond>\n";
+ $allowed[$allow] = 1;
+ }
+ if ($block =~/\b(?:if|for|while)\b/) {
+ #print "APW: ALLOWED: block<$block>\n";
+ $allowed[$allow] = 1;
+ }
+ if (statement_block_size($block) > 1) {
+ #print "APW: ALLOWED: lines block<$block>\n";
+ $allowed[$allow] = 1;
+ }
+ $allow++;
+ }
+ if ($seen) {
+ my $sum_allowed = 0;
+ foreach (@allowed) {
+ $sum_allowed += $_;
+ }
+ if ($sum_allowed == 0) {
+ WARN("BRACES",
+ "braces {} are not necessary for any arm of this statement\n" . $herectx);
+ } elsif ($sum_allowed != $allow &&
+ $seen != $allow) {
+ CHK("BRACES",
+ "braces {} should be used on all arms of this statement\n" . $herectx);
+ }
+ }
+ }
+ }
+ if (!defined $suppress_ifbraces{$linenr - 1} &&
+ $line =~ /\b(if|while|for|else)\b/) {
+ my $allowed = 0;
+
+ # Check the pre-context.
+ if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) {
+ #print "APW: ALLOWED: pre<$1>\n";
+ $allowed = 1;
+ }
+
+ my ($level, $endln, @chunks) =
+ ctx_statement_full($linenr, $realcnt, $-[0]);
+
+ # Check the condition.
+ my ($cond, $block) = @{$chunks[0]};
+ #print "CHECKING<$linenr> cond<$cond> block<$block>\n";
+ if (defined $cond) {
+ substr($block, 0, length($cond), '');
+ }
+ if (statement_lines($cond) > 1) {
+ #print "APW: ALLOWED: cond<$cond>\n";
+ $allowed = 1;
+ }
+ if ($block =~/\b(?:if|for|while)\b/) {
+ #print "APW: ALLOWED: block<$block>\n";
+ $allowed = 1;
+ }
+ if (statement_block_size($block) > 1) {
+ #print "APW: ALLOWED: lines block<$block>\n";
+ $allowed = 1;
+ }
+ # Check the post-context.
+ if (defined $chunks[1]) {
+ my ($cond, $block) = @{$chunks[1]};
+ if (defined $cond) {
+ substr($block, 0, length($cond), '');
+ }
+ if ($block =~ /^\s*\{/) {
+ #print "APW: ALLOWED: chunk-1 block<$block>\n";
+ $allowed = 1;
+ }
+ }
+ if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) {
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($block);
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ WARN("BRACES",
+ "braces {} are not necessary for single statement blocks\n" . $herectx);
+ }
+ }
+
+# check for unnecessary blank lines around braces
+ if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) {
+ if (CHK("BRACES",
+ "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) &&
+ $fix && $prevrawline =~ /^\+/) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ }
+ }
+ if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) {
+ if (CHK("BRACES",
+ "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) &&
+ $fix) {
+ fix_delete_line($fixlinenr, $rawline);
+ }
+ }
+
+# no volatiles please
+ my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b};
+ if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) {
+ WARN("VOLATILE",
+ "Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt\n" . $herecurr);
+ }
+
+# Check for user-visible strings broken across lines, which breaks the ability
+# to grep for the string. Make exceptions when the previous string ends in a
+# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{'
+# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value
+ if ($line =~ /^\+\s*$String/ &&
+ $prevline =~ /"\s*$/ &&
+ $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) {
+ if (WARN("SPLIT_STRING",
+ "quoted string split across lines\n" . $hereprev) &&
+ $fix &&
+ $prevrawline =~ /^\+.*"\s*$/ &&
+ $last_coalesced_string_linenr != $linenr - 1) {
+ my $extracted_string = get_quoted_string($line, $rawline);
+ my $comma_close = "";
+ if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) {
+ $comma_close = $1;
+ }
+
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ fix_delete_line($fixlinenr, $rawline);
+ my $fixedline = $prevrawline;
+ $fixedline =~ s/"\s*$//;
+ $fixedline .= substr($extracted_string, 1) . trim($comma_close);
+ fix_insert_line($fixlinenr - 1, $fixedline);
+ $fixedline = $rawline;
+ $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//;
+ if ($fixedline !~ /\+\s*$/) {
+ fix_insert_line($fixlinenr, $fixedline);
+ }
+ $last_coalesced_string_linenr = $linenr;
+ }
+ }
+
+# check for missing a space in a string concatenation
+ if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) {
+ WARN('MISSING_SPACE',
+ "break quoted strings at a space character\n" . $hereprev);
+ }
+
+# check for spaces before a quoted newline
+ if ($rawline =~ /^.*\".*\s\\n/) {
+ if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE",
+ "unnecessary whitespace before a quoted newline\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/;
+ }
+
+ }
+
+# concatenated string without spaces between elements
+ if ($line =~ /$String[A-Z_]/ || $line =~ /[A-Za-z0-9_]$String/) {
+ CHK("CONCATENATED_STRING",
+ "Concatenated strings should use spaces between elements\n" . $herecurr);
+ }
+
+# uncoalesced string fragments
+ if ($line =~ /$String\s*"/) {
+ WARN("STRING_FRAGMENTS",
+ "Consecutive strings are generally better as a single string\n" . $herecurr);
+ }
+
+# check for %L{u,d,i} and 0x%[udi] in strings
+ my $string;
+ while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) {
+ $string = substr($rawline, $-[1], $+[1] - $-[1]);
+ $string =~ s/%%/__/g;
+ if ($string =~ /(?<!%)%[\*\d\.\$]*L[udi]/) {
+ WARN("PRINTF_L",
+ "\%Ld/%Lu are not-standard C, use %lld/%llu\n" . $herecurr);
+ last;
+ }
+ if ($string =~ /0x%[\*\d\.\$\Llzth]*[udi]/) {
+ ERROR("PRINTF_0xDECIMAL",
+ "Prefixing 0x with decimal output is defective\n" . $herecurr);
+ }
+ }
+
+# check for line continuations in quoted strings with odd counts of "
+ if ($rawline =~ /\\$/ && $rawline =~ tr/"/"/ % 2) {
+ WARN("LINE_CONTINUATIONS",
+ "Avoid line continuations in quoted strings\n" . $herecurr);
+ }
+
+# warn about #if 0
+ if ($line =~ /^.\s*\#\s*if\s+0\b/) {
+ CHK("REDUNDANT_CODE",
+ "if this code is redundant consider removing it\n" .
+ $herecurr);
+ }
+
+# check for needless "if (<foo>) fn(<foo>)" uses
+ if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) {
+ my $tested = quotemeta($1);
+ my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;';
+ if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) {
+ my $func = $1;
+ if (WARN('NEEDLESS_IF',
+ "$func(NULL) is safe and this check is probably not required\n" . $hereprev) &&
+ $fix) {
+ my $do_fix = 1;
+ my $leading_tabs = "";
+ my $new_leading_tabs = "";
+ if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) {
+ $leading_tabs = $1;
+ } else {
+ $do_fix = 0;
+ }
+ if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) {
+ $new_leading_tabs = $1;
+ if (length($leading_tabs) + 1 ne length($new_leading_tabs)) {
+ $do_fix = 0;
+ }
+ } else {
+ $do_fix = 0;
+ }
+ if ($do_fix) {
+ fix_delete_line($fixlinenr - 1, $prevrawline);
+ $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/;
+ }
+ }
+ }
+ }
+
+# check for unnecessary "Out of Memory" messages
+ if ($line =~ /^\+.*\b$logFunctions\s*\(/ &&
+ $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ &&
+ (defined $1 || defined $3) &&
+ $linenr > 3) {
+ my $testval = $2;
+ my $testline = $lines[$linenr - 3];
+
+ my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0);
+# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n");
+
+ if ($c =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*(?:devm_)?(?:[kv][czm]alloc(?:_node|_array)?\b|kstrdup|(?:dev_)?alloc_skb)/) {
+ WARN("OOM_MESSAGE",
+ "Possible unnecessary 'out of memory' message\n" . $hereprev);
+ }
+ }
+
+# check for logging functions with KERN_<LEVEL>
+ if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ &&
+ $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) {
+ my $level = $1;
+ if (WARN("UNNECESSARY_KERN_LEVEL",
+ "Possible unnecessary $level\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\s*$level\s*//;
+ }
+ }
+
+# check for mask then right shift without a parentheses
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ &&
+ $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so
+ WARN("MASK_THEN_SHIFT",
+ "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr);
+ }
+
+# check for pointer comparisons to NULL
+ if ($^V && $^V ge 5.10.0) {
+ while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) {
+ my $val = $1;
+ my $equal = "!";
+ $equal = "" if ($4 eq "!=");
+ if (CHK("COMPARISON_TO_NULL",
+ "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/;
+ }
+ }
+ }
+
+# check for bad placement of section $InitAttribute (e.g.: __initdata)
+ if ($line =~ /(\b$InitAttribute\b)/) {
+ my $attr = $1;
+ if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) {
+ my $ptr = $1;
+ my $var = $2;
+ if ((($ptr =~ /\b(union|struct)\s+$attr\b/ &&
+ ERROR("MISPLACED_INIT",
+ "$attr should be placed after $var\n" . $herecurr)) ||
+ ($ptr !~ /\b(union|struct)\s+$attr\b/ &&
+ WARN("MISPLACED_INIT",
+ "$attr should be placed after $var\n" . $herecurr))) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e;
+ }
+ }
+ }
+
+# check for $InitAttributeData (ie: __initdata) with const
+ if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) {
+ my $attr = $1;
+ $attr =~ /($InitAttributePrefix)(.*)/;
+ my $attr_prefix = $1;
+ my $attr_type = $2;
+ if (ERROR("INIT_ATTRIBUTE",
+ "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/$InitAttributeData/${attr_prefix}initconst/;
+ }
+ }
+
+# check for $InitAttributeConst (ie: __initconst) without const
+ if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) {
+ my $attr = $1;
+ if (ERROR("INIT_ATTRIBUTE",
+ "Use of $attr requires a separate use of const\n" . $herecurr) &&
+ $fix) {
+ my $lead = $fixed[$fixlinenr] =~
+ /(^\+\s*(?:static\s+))/;
+ $lead = rtrim($1);
+ $lead = "$lead " if ($lead !~ /^\+$/);
+ $lead = "${lead}const ";
+ $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/;
+ }
+ }
+
+# check for __read_mostly with const non-pointer (should just be const)
+ if ($line =~ /\b__read_mostly\b/ &&
+ $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) {
+ if (ERROR("CONST_READ_MOSTLY",
+ "Invalid use of __read_mostly with const type\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//;
+ }
+ }
+
+# don't use __constant_<foo> functions outside of include/uapi/
+ if ($realfile !~ m@^include/uapi/@ &&
+ $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) {
+ my $constant_func = $1;
+ my $func = $constant_func;
+ $func =~ s/^__constant_//;
+ if (WARN("CONSTANT_CONVERSION",
+ "$constant_func should be $func\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g;
+ }
+ }
+
+# prefer usleep_range over udelay
+ if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) {
+ my $delay = $1;
+ # ignore udelay's < 10, however
+ if (! ($delay < 10) ) {
+ CHK("USLEEP_RANGE",
+ "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $herecurr);
+ }
+ if ($delay > 2000) {
+ WARN("LONG_UDELAY",
+ "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr);
+ }
+ }
+
+# warn about unexpectedly long msleep's
+ if ($line =~ /\bmsleep\s*\((\d+)\);/) {
+ if ($1 < 20) {
+ WARN("MSLEEP",
+ "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $herecurr);
+ }
+ }
+
+# check for comparisons of jiffies
+ if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) {
+ WARN("JIFFIES_COMPARISON",
+ "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr);
+ }
+
+# check for comparisons of get_jiffies_64()
+ if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) {
+ WARN("JIFFIES_COMPARISON",
+ "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr);
+ }
+
+# warn about #ifdefs in C files
+# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) {
+# print "#ifdef in C files should be avoided\n";
+# print "$herecurr";
+# $clean = 0;
+# }
+
+# warn about spacing in #ifdefs
+ if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) {
+ if (ERROR("SPACING",
+ "exactly one space required after that #$1\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /;
+ }
+
+ }
+
+# check for spinlock_t definitions without a comment.
+ if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ ||
+ $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) {
+ my $which = $1;
+ if (!ctx_has_comment($first_line, $linenr)) {
+ CHK("UNCOMMENTED_DEFINITION",
+ "$1 definition without comment\n" . $herecurr);
+ }
+ }
+# check for memory barriers without a comment.
+
+ my $barriers = qr{
+ mb|
+ rmb|
+ wmb|
+ read_barrier_depends
+ }x;
+ my $barrier_stems = qr{
+ mb__before_atomic|
+ mb__after_atomic|
+ store_release|
+ load_acquire|
+ store_mb|
+ (?:$barriers)
+ }x;
+ my $all_barriers = qr{
+ (?:$barriers)|
+ smp_(?:$barrier_stems)|
+ virt_(?:$barrier_stems)
+ }x;
+
+ if ($line =~ /\b(?:$all_barriers)\s*\(/) {
+ if (!ctx_has_comment($first_line, $linenr)) {
+ WARN("MEMORY_BARRIER",
+ "memory barrier without comment\n" . $herecurr);
+ }
+ }
+
+ my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x;
+
+ if ($realfile !~ m@^include/asm-generic/@ &&
+ $realfile !~ m@/barrier\.h$@ &&
+ $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ &&
+ $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) {
+ WARN("MEMORY_BARRIER",
+ "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr);
+ }
+
+# check for waitqueue_active without a comment.
+ if ($line =~ /\bwaitqueue_active\s*\(/) {
+ if (!ctx_has_comment($first_line, $linenr)) {
+ WARN("WAITQUEUE_ACTIVE",
+ "waitqueue_active without comment\n" . $herecurr);
+ }
+ }
+
+# Check for expedited grace periods that interrupt non-idle non-nohz
+# online CPUs. These expedited can therefore degrade real-time response
+# if used carelessly, and should be avoided where not absolutely
+# needed. It is always OK to use synchronize_rcu_expedited() and
+# synchronize_sched_expedited() at boot time (before real-time applications
+# start) and in error situations where real-time response is compromised in
+# any case. Note that synchronize_srcu_expedited() does -not- interrupt
+# other CPUs, so don't warn on uses of synchronize_srcu_expedited().
+# Of course, nothing comes for free, and srcu_read_lock() and
+# srcu_read_unlock() do contain full memory barriers in payment for
+# synchronize_srcu_expedited() non-interruption properties.
+ if ($line =~ /\b(synchronize_rcu_expedited|synchronize_sched_expedited)\(/) {
+ WARN("EXPEDITED_RCU_GRACE_PERIOD",
+ "expedited RCU grace periods should be avoided where they can degrade real-time response\n" . $herecurr);
+
+ }
+
+# check of hardware specific defines
+ if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) {
+ CHK("ARCH_DEFINES",
+ "architecture specific defines should be avoided\n" . $herecurr);
+ }
+
+# Check that the storage class is at the beginning of a declaration
+ if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage\b/) {
+ WARN("STORAGE_CLASS",
+ "storage class should be at the beginning of the declaration\n" . $herecurr)
+ }
+
+# check the location of the inline attribute, that it is between
+# storage class and type.
+ if ($line =~ /\b$Type\s+$Inline\b/ ||
+ $line =~ /\b$Inline\s+$Storage\b/) {
+ ERROR("INLINE_LOCATION",
+ "inline keyword should sit between storage class and type\n" . $herecurr);
+ }
+
+# Check for __inline__ and __inline, prefer inline
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b(__inline__|__inline)\b/) {
+ if (WARN("INLINE",
+ "plain inline is preferred over $1\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/;
+
+ }
+ }
+
+# Check for __attribute__ packed, prefer __packed
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) {
+ WARN("PREFER_PACKED",
+ "__packed is preferred over __attribute__((packed))\n" . $herecurr);
+ }
+
+# Check for __attribute__ aligned, prefer __aligned
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b__attribute__\s*\(\s*\(.*aligned/) {
+ WARN("PREFER_ALIGNED",
+ "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr);
+ }
+
+# Check for __attribute__ format(printf, prefer __printf
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) {
+ if (WARN("PREFER_PRINTF",
+ "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf\s*,\s*(.*)\)\s*\)\s*\)/"__printf(" . trim($1) . ")"/ex;
+
+ }
+ }
+
+# Check for __attribute__ format(scanf, prefer __scanf
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) {
+ if (WARN("PREFER_SCANF",
+ "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\s*,\s*(.*)\)\s*\)\s*\)/"__scanf(" . trim($1) . ")"/ex;
+ }
+ }
+
+# Check for __attribute__ weak, or __weak declarations (may have link issues)
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ &&
+ ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ ||
+ $line =~ /\b__weak\b/)) {
+ ERROR("WEAK_DECLARATION",
+ "Using weak declarations can have unintended link defects\n" . $herecurr);
+ }
+
+# check for c99 types like uint8_t used outside of uapi/
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) {
+ my $type = $1;
+ if ($type =~ /\b($typeC99Typedefs)\b/) {
+ $type = $1;
+ my $kernel_type = 'u';
+ $kernel_type = 's' if ($type =~ /^_*[si]/);
+ $type =~ /(\d+)/;
+ $kernel_type .= $1;
+ if (CHK("PREFER_KERNEL_TYPES",
+ "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/;
+ }
+ }
+ }
+
+# check for cast of C90 native int or longer types constants
+ if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) {
+ my $cast = $1;
+ my $const = $2;
+ if (WARN("TYPECAST_INT_CONSTANT",
+ "Unnecessary typecast of c90 int constant\n" . $herecurr) &&
+ $fix) {
+ my $suffix = "";
+ my $newconst = $const;
+ $newconst =~ s/${Int_type}$//;
+ $suffix .= 'U' if ($cast =~ /\bunsigned\b/);
+ if ($cast =~ /\blong\s+long\b/) {
+ $suffix .= 'LL';
+ } elsif ($cast =~ /\blong\b/) {
+ $suffix .= 'L';
+ }
+ $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/;
+ }
+ }
+
+# check for sizeof(&)
+ if ($line =~ /\bsizeof\s*\(\s*\&/) {
+ WARN("SIZEOF_ADDRESS",
+ "sizeof(& should be avoided\n" . $herecurr);
+ }
+
+# check for sizeof without parenthesis
+ if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) {
+ if (WARN("SIZEOF_PARENTHESIS",
+ "sizeof $1 should be sizeof($1)\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex;
+ }
+ }
+
+# check for struct spinlock declarations
+ if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) {
+ WARN("USE_SPINLOCK_T",
+ "struct spinlock should be spinlock_t\n" . $herecurr);
+ }
+
+# check for seq_printf uses that could be seq_puts
+ if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) {
+ my $fmt = get_quoted_string($line, $rawline);
+ $fmt =~ s/%%//g;
+ if ($fmt !~ /%/) {
+ if (WARN("PREFER_SEQ_PUTS",
+ "Prefer seq_puts to seq_printf\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/;
+ }
+ }
+ }
+
+# Check for misused memsets
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) {
+
+ my $ms_addr = $2;
+ my $ms_val = $7;
+ my $ms_size = $12;
+
+ if ($ms_size =~ /^(0x|)0$/i) {
+ ERROR("MEMSET",
+ "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n");
+ } elsif ($ms_size =~ /^(0x|)1$/i) {
+ WARN("MEMSET",
+ "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n");
+ }
+ }
+
+# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar)
+# if ($^V && $^V ge 5.10.0 &&
+# defined $stat &&
+# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) {
+# if (WARN("PREFER_ETHER_ADDR_COPY",
+# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") &&
+# $fix) {
+# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/;
+# }
+# }
+
+# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar)
+# if ($^V && $^V ge 5.10.0 &&
+# defined $stat &&
+# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) {
+# WARN("PREFER_ETHER_ADDR_EQUAL",
+# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n")
+# }
+
+# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr
+# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr
+# if ($^V && $^V ge 5.10.0 &&
+# defined $stat &&
+# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) {
+#
+# my $ms_val = $7;
+#
+# if ($ms_val =~ /^(?:0x|)0+$/i) {
+# if (WARN("PREFER_ETH_ZERO_ADDR",
+# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") &&
+# $fix) {
+# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/;
+# }
+# } elsif ($ms_val =~ /^(?:0xff|255)$/i) {
+# if (WARN("PREFER_ETH_BROADCAST_ADDR",
+# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") &&
+# $fix) {
+# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/;
+# }
+# }
+# }
+
+# typecasts on min/max could be min_t/max_t
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) {
+ if (defined $2 || defined $7) {
+ my $call = $1;
+ my $cast1 = deparenthesize($2);
+ my $arg1 = $3;
+ my $cast2 = deparenthesize($7);
+ my $arg2 = $8;
+ my $cast;
+
+ if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) {
+ $cast = "$cast1 or $cast2";
+ } elsif ($cast1 ne "") {
+ $cast = $cast1;
+ } else {
+ $cast = $cast2;
+ }
+ WARN("MINMAX",
+ "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n");
+ }
+ }
+
+# check usleep_range arguments
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) {
+ my $min = $1;
+ my $max = $7;
+ if ($min eq $max) {
+ WARN("USLEEP_RANGE",
+ "usleep_range should not use min == max args; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n");
+ } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ &&
+ $min > $max) {
+ WARN("USLEEP_RANGE",
+ "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n");
+ }
+ }
+
+# check for naked sscanf
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $line =~ /\bsscanf\b/ &&
+ ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ &&
+ $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ &&
+ $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) {
+ my $lc = $stat =~ tr@\n@@;
+ $lc = $lc + $linenr;
+ my $stat_real = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+ WARN("NAKED_SSCANF",
+ "unchecked sscanf return value\n" . "$here\n$stat_real\n");
+ }
+
+# check for simple sscanf that should be kstrto<foo>
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $line =~ /\bsscanf\b/) {
+ my $lc = $stat =~ tr@\n@@;
+ $lc = $lc + $linenr;
+ my $stat_real = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+ if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) {
+ my $format = $6;
+ my $count = $format =~ tr@%@%@;
+ if ($count == 1 &&
+ $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) {
+ WARN("SSCANF_TO_KSTRTO",
+ "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n");
+ }
+ }
+ }
+
+# check for new externs in .h files.
+ if ($realfile =~ /\.h$/ &&
+ $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) {
+ if (CHK("AVOID_EXTERNS",
+ "extern prototypes should be avoided in .h files\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/;
+ }
+ }
+
+# check for new externs in .c files.
+ if ($realfile =~ /\.c$/ && defined $stat &&
+ $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s)
+ {
+ my $function_name = $1;
+ my $paren_space = $2;
+
+ my $s = $stat;
+ if (defined $cond) {
+ substr($s, 0, length($cond), '');
+ }
+ if ($s =~ /^\s*;/ &&
+ $function_name ne 'uninitialized_var')
+ {
+ WARN("AVOID_EXTERNS",
+ "externs should be avoided in .c files\n" . $herecurr);
+ }
+
+ if ($paren_space =~ /\n/) {
+ WARN("FUNCTION_ARGUMENTS",
+ "arguments for function declarations should follow identifier\n" . $herecurr);
+ }
+
+ } elsif ($realfile =~ /\.c$/ && defined $stat &&
+ $stat =~ /^.\s*extern\s+/)
+ {
+ WARN("AVOID_EXTERNS",
+ "externs should be avoided in .c files\n" . $herecurr);
+ }
+
+ if ($realfile =~ /\.[ch]$/ && defined $stat &&
+ $stat =~ /^.\s*(?:extern\s+)?$Type\s*$Ident\s*\(\s*([^{]+)\s*\)\s*;/s &&
+ $1 ne "void") {
+ my $args = trim($1);
+ while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) {
+ my $arg = trim($1);
+ if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) {
+ WARN("FUNCTION_ARGUMENTS",
+ "function definition argument '$arg' should also have an identifier name\n" . $herecurr);
+ }
+ }
+ }
+
+# checks for new __setup's
+ if ($rawline =~ /\b__setup\("([^"]*)"/) {
+ my $name = $1;
+
+ if (!grep(/$name/, @setup_docs)) {
+ CHK("UNDOCUMENTED_SETUP",
+ "__setup appears un-documented -- check Documentation/kernel-parameters.txt\n" . $herecurr);
+ }
+ }
+
+# check for pointless casting of kmalloc return
+ if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) {
+ WARN("UNNECESSARY_CASTS",
+ "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr);
+ }
+
+# alloc style
+# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...)
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*([kv][mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) {
+ CHK("ALLOC_SIZEOF_STRUCT",
+ "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr);
+ }
+
+# check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) {
+ my $oldfunc = $3;
+ my $a1 = $4;
+ my $a2 = $10;
+ my $newfunc = "kmalloc_array";
+ $newfunc = "kcalloc" if ($oldfunc eq "kzalloc");
+ my $r1 = $a1;
+ my $r2 = $a2;
+ if ($a1 =~ /^sizeof\s*\S/) {
+ $r1 = $a2;
+ $r2 = $a1;
+ }
+ if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ &&
+ !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) {
+ if (WARN("ALLOC_WITH_MULTIPLY",
+ "Prefer $newfunc over $oldfunc with multiply\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e;
+
+ }
+ }
+ }
+
+# check for krealloc arg reuse
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*\1\s*,/) {
+ WARN("KREALLOC_ARG_REUSE",
+ "Reusing the krealloc arg is almost always a bug\n" . $herecurr);
+ }
+
+# check for alloc argument mismatch
+ if ($line =~ /\b(kcalloc|kmalloc_array)\s*\(\s*sizeof\b/) {
+ WARN("ALLOC_ARRAY_ARGS",
+ "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr);
+ }
+
+# check for multiple semicolons
+ if ($line =~ /;\s*;\s*$/) {
+ if (WARN("ONE_SEMICOLON",
+ "Statements terminations use 1 semicolon\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g;
+ }
+ }
+
+# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi
+ if ($realfile !~ m@^include/uapi/@ &&
+ $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) {
+ my $ull = "";
+ $ull = "_ULL" if (defined($1) && $1 =~ /ll/i);
+ if (CHK("BIT_MACRO",
+ "Prefer using the BIT$ull macro\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/;
+ }
+ }
+
+# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE
+ if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(CONFIG_[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) {
+ my $config = $1;
+ if (WARN("PREFER_IS_ENABLED",
+ "Prefer IS_ENABLED(<FOO>) to CONFIG_<FOO> || CONFIG_<FOO>_MODULE\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)";
+ }
+ }
+
+# check for case / default statements not preceded by break/fallthrough/switch
+ if ($line =~ /^.\s*(?:case\s+(?:$Ident|$Constant)\s*|default):/) {
+ my $has_break = 0;
+ my $has_statement = 0;
+ my $count = 0;
+ my $prevline = $linenr;
+ while ($prevline > 1 && ($file || $count < 3) && !$has_break) {
+ $prevline--;
+ my $rline = $rawlines[$prevline - 1];
+ my $fline = $lines[$prevline - 1];
+ last if ($fline =~ /^\@\@/);
+ next if ($fline =~ /^\-/);
+ next if ($fline =~ /^.(?:\s*(?:case\s+(?:$Ident|$Constant)[\s$;]*|default):[\s$;]*)*$/);
+ $has_break = 1 if ($rline =~ /fall[\s_-]*(through|thru)/i);
+ next if ($fline =~ /^.[\s$;]*$/);
+ $has_statement = 1;
+ $count++;
+ $has_break = 1 if ($fline =~ /\bswitch\b|\b(?:break\s*;[\s$;]*$|return\b|goto\b|continue\b)/);
+ }
+ if (!$has_break && $has_statement) {
+ WARN("MISSING_BREAK",
+ "Possible switch case/default not preceeded by break or fallthrough comment\n" . $herecurr);
+ }
+ }
+
+# check for switch/default statements without a break;
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) {
+ my $ctx = '';
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($stat);
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+ WARN("DEFAULT_NO_BREAK",
+ "switch default: should use break\n" . $herectx);
+ }
+
+# check for gcc specific __FUNCTION__
+ if ($line =~ /\b__FUNCTION__\b/) {
+ if (WARN("USE_FUNC",
+ "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g;
+ }
+ }
+
+# check for uses of __DATE__, __TIME__, __TIMESTAMP__
+ while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) {
+ ERROR("DATE_TIME",
+ "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr);
+ }
+
+# check for use of yield()
+ if ($line =~ /\byield\s*\(\s*\)/) {
+ WARN("YIELD",
+ "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr);
+ }
+
+# check for comparisons against true and false
+ if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) {
+ my $lead = $1;
+ my $arg = $2;
+ my $test = $3;
+ my $otype = $4;
+ my $trail = $5;
+ my $op = "!";
+
+ ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i);
+
+ my $type = lc($otype);
+ if ($type =~ /^(?:true|false)$/) {
+ if (("$test" eq "==" && "$type" eq "true") ||
+ ("$test" eq "!=" && "$type" eq "false")) {
+ $op = "";
+ }
+
+ CHK("BOOL_COMPARISON",
+ "Using comparison to $otype is error prone\n" . $herecurr);
+
+## maybe suggesting a correct construct would better
+## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr);
+
+ }
+ }
+
+# check for semaphores initialized locked
+ if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) {
+ WARN("CONSIDER_COMPLETION",
+ "consider using a completion\n" . $herecurr);
+ }
+
+# recommend kstrto* over simple_strto* and strict_strto*
+ if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) {
+ WARN("CONSIDER_KSTRTO",
+ "$1 is obsolete, use k$3 instead\n" . $herecurr);
+ }
+
+# check for __initcall(), use device_initcall() explicitly or more appropriate function please
+ if ($line =~ /^.\s*__initcall\s*\(/) {
+ WARN("USE_DEVICE_INITCALL",
+ "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr);
+ }
+
+# check for various structs that are normally const (ops, kgdb, device_tree)
+ if ($line !~ /\bconst\b/ &&
+ $line =~ /\bstruct\s+($const_structs)\b/) {
+ WARN("CONST_STRUCT",
+ "struct $1 should normally be const\n" .
+ $herecurr);
+ }
+
+# use of NR_CPUS is usually wrong
+# ignore definitions of NR_CPUS and usage to define arrays as likely right
+ if ($line =~ /\bNR_CPUS\b/ &&
+ $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ &&
+ $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ &&
+ $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ &&
+ $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ &&
+ $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/)
+ {
+ WARN("NR_CPUS",
+ "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr);
+ }
+
+# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong.
+ if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) {
+ ERROR("DEFINE_ARCH_HAS",
+ "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr);
+ }
+
+# likely/unlikely comparisons similar to "(likely(foo) > 0)"
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) {
+ WARN("LIKELY_MISUSE",
+ "Using $1 should generally have parentheses around the comparison\n" . $herecurr);
+ }
+
+# whine mightly about in_atomic
+ if ($line =~ /\bin_atomic\s*\(/) {
+ if ($realfile =~ m@^drivers/@) {
+ ERROR("IN_ATOMIC",
+ "do not use in_atomic in drivers\n" . $herecurr);
+ } elsif ($realfile !~ m@^kernel/@) {
+ WARN("IN_ATOMIC",
+ "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr);
+ }
+ }
+
+# whine about ACCESS_ONCE
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\bACCESS_ONCE\s*$balanced_parens\s*(=(?!=))?\s*($FuncArg)?/) {
+ my $par = $1;
+ my $eq = $2;
+ my $fun = $3;
+ $par =~ s/^\(\s*(.*)\s*\)$/$1/;
+ if (defined($eq)) {
+ if (WARN("PREFER_WRITE_ONCE",
+ "Prefer WRITE_ONCE(<FOO>, <BAR>) over ACCESS_ONCE(<FOO>) = <BAR>\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)\s*$eq\s*\Q$fun\E/WRITE_ONCE($par, $fun)/;
+ }
+ } else {
+ if (WARN("PREFER_READ_ONCE",
+ "Prefer READ_ONCE(<FOO>) over ACCESS_ONCE(<FOO>)\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)/READ_ONCE($par)/;
+ }
+ }
+ }
+
+# check for lockdep_set_novalidate_class
+ if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ ||
+ $line =~ /__lockdep_no_validate__\s*\)/ ) {
+ if ($realfile !~ m@^kernel/lockdep@ &&
+ $realfile !~ m@^include/linux/lockdep@ &&
+ $realfile !~ m@^drivers/base/core@) {
+ ERROR("LOCKDEP",
+ "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr);
+ }
+ }
+
+ if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ ||
+ $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) {
+ WARN("EXPORTED_WORLD_WRITABLE",
+ "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr);
+ }
+
+# Mode permission misuses where it seems decimal should be octal
+# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $line =~ /$mode_perms_search/) {
+ foreach my $entry (@mode_permission_funcs) {
+ my $func = $entry->[0];
+ my $arg_pos = $entry->[1];
+
+ my $lc = $stat =~ tr@\n@@;
+ $lc = $lc + $linenr;
+ my $stat_real = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+
+ my $skip_args = "";
+ if ($arg_pos > 1) {
+ $arg_pos--;
+ $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}";
+ }
+ my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]";
+ if ($stat =~ /$test/) {
+ my $val = $1;
+ $val = $6 if ($skip_args ne "");
+ if (($val =~ /^$Int$/ && $val !~ /^$Octal$/) ||
+ ($val =~ /^$Octal$/ && length($val) ne 4)) {
+ ERROR("NON_OCTAL_PERMISSIONS",
+ "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real);
+ }
+ if ($val =~ /^$Octal$/ && (oct($val) & 02)) {
+ ERROR("EXPORTED_WORLD_WRITABLE",
+ "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real);
+ }
+ }
+ }
+ }
+
+# check for uses of S_<PERMS> that could be octal for readability
+ if ($line =~ /\b$mode_perms_string_search\b/) {
+ my $val = "";
+ my $oval = "";
+ my $to = 0;
+ my $curpos = 0;
+ my $lastpos = 0;
+ while ($line =~ /\b(($mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) {
+ $curpos = pos($line);
+ my $match = $2;
+ my $omatch = $1;
+ last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos));
+ $lastpos = $curpos;
+ $to |= $mode_permission_string_types{$match};
+ $val .= '\s*\|\s*' if ($val ne "");
+ $val .= $match;
+ $oval .= $omatch;
+ }
+ $oval =~ s/^\s*\|\s*//;
+ $oval =~ s/\s*\|\s*$//;
+ my $octal = sprintf("%04o", $to);
+ if (WARN("SYMBOLIC_PERMS",
+ "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/$val/$octal/;
+ }
+ }
+
+# validate content of MODULE_LICENSE against list from include/linux/module.h
+ if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) {
+ my $extracted_string = get_quoted_string($line, $rawline);
+ my $valid_licenses = qr{
+ GPL|
+ GPL\ v2|
+ GPL\ and\ additional\ rights|
+ Dual\ BSD/GPL|
+ Dual\ MIT/GPL|
+ Dual\ MPL/GPL|
+ Proprietary
+ }x;
+ if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) {
+ WARN("MODULE_LICENSE",
+ "unknown module license " . $extracted_string . "\n" . $herecurr);
+ }
+ }
+ }
+
+ # If we have no input at all, then there is nothing to report on
+ # so just keep quiet.
+ if ($#rawlines == -1) {
+ exit(0);
+ }
+
+ # In mailback mode only produce a report in the negative, for
+ # things that appear to be patches.
+ if ($mailback && ($clean == 1 || !$is_patch)) {
+ exit(0);
+ }
+
+ # This is not a patch, and we are are in 'no-patch' mode so
+ # just keep quiet.
+ if (!$chk_patch && !$is_patch) {
+ exit(0);
+ }
+
+ if (!$is_patch && $file !~ /cover-letter\.patch$/) {
+ ERROR("NOT_UNIFIED_DIFF",
+ "Does not appear to be a unified-diff format patch\n");
+ }
+ if ($is_patch && $has_commit_log && $chk_signoff && $signoff == 0) {
+ ERROR("MISSING_SIGN_OFF",
+ "Missing Signed-off-by: line(s)\n");
+ }
+
+ print report_dump();
+ if ($summary && !($clean == 1 && $quiet == 1)) {
+ print "$filename " if ($summary_file);
+ print "total: $cnt_error errors, $cnt_warn warnings, " .
+ (($check)? "$cnt_chk checks, " : "") .
+ "$cnt_lines lines checked\n";
+ }
+
+ if ($quiet == 0) {
+ # If there were any defects found and not already fixing them
+ if (!$clean and !$fix) {
+ print << "EOM"
+
+NOTE: For some of the reported defects, checkpatch may be able to
+ mechanically convert to the typical style using --fix or --fix-inplace.
+EOM
+ }
+ # If there were whitespace errors which cleanpatch can fix
+ # then suggest that.
+ if ($rpt_cleaners) {
+ $rpt_cleaners = 0;
+ print << "EOM"
+
+NOTE: Whitespace errors detected.
+ You may wish to use scripts/cleanpatch or scripts/cleanfile
+EOM
+ }
+ }
+
+ if ($clean == 0 && $fix &&
+ ("@rawlines" ne "@fixed" ||
+ $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) {
+ my $newfile = $filename;
+ $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace);
+ my $linecount = 0;
+ my $f;
+
+ @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted);
+
+ open($f, '>', $newfile)
+ or die "$P: Can't open $newfile for write\n";
+ foreach my $fixed_line (@fixed) {
+ $linecount++;
+ if ($file) {
+ if ($linecount > 3) {
+ $fixed_line =~ s/^\+//;
+ print $f $fixed_line . "\n";
+ }
+ } else {
+ print $f $fixed_line . "\n";
+ }
+ }
+ close($f);
+
+ if (!$quiet) {
+ print << "EOM";
+
+Wrote EXPERIMENTAL --fix correction(s) to '$newfile'
+
+Do _NOT_ trust the results written to this file.
+Do _NOT_ submit these changes without inspecting them for correctness.
+
+This EXPERIMENTAL file is simply a convenience to help rewrite patches.
+No warranties, expressed or implied...
+EOM
+ }
+ }
+
+ if ($quiet == 0) {
+ print "\n";
+ if ($clean == 1) {
+ print "$vname has no obvious style problems and is ready for submission.\n";
+ } else {
+ print "$vname has style problems, please review.\n";
+ }
+ }
+ return $clean;
+}
diff --git a/util/const_structs.checkpatch b/util/const_structs.checkpatch
new file mode 100644
index 0000000..ac5f126
--- /dev/null
+++ b/util/const_structs.checkpatch
@@ -0,0 +1,64 @@
+acpi_dock_ops
+address_space_operations
+backlight_ops
+block_device_operations
+clk_ops
+comedi_lrange
+component_ops
+dentry_operations
+dev_pm_ops
+dma_map_ops
+driver_info
+drm_connector_funcs
+drm_encoder_funcs
+drm_encoder_helper_funcs
+ethtool_ops
+extent_io_ops
+file_lock_operations
+file_operations
+hv_ops
+ide_dma_ops
+ide_port_ops
+inode_operations
+intel_dvo_dev_ops
+irq_domain_ops
+item_operations
+iwl_cfg
+iwl_ops
+kgdb_arch
+kgdb_io
+kset_uevent_ops
+lock_manager_operations
+machine_desc
+microcode_ops
+mlxsw_reg_info
+mtrr_ops
+neigh_ops
+net_device_ops
+nlmsvc_binding
+nvkm_device_chip
+of_device_id
+pci_raw_ops
+pipe_buf_operations
+platform_hibernation_ops
+platform_suspend_ops
+proto_ops
+regmap_access_table
+rpc_pipe_ops
+rtc_class_ops
+sd_desc
+seq_operations
+sirfsoc_padmux
+snd_ac97_build_ops
+snd_soc_component_driver
+soc_pcmcia_socket_ops
+stacktrace_ops
+sysfs_ops
+tty_operations
+uart_ops
+usb_mon_operations
+v4l2_ctrl_ops
+v4l2_ioctl_ops
+vm_operations_struct
+wacom_features
+wd_ops
diff --git a/util/spelling.txt b/util/spelling.txt
new file mode 100644
index 0000000..163c720
--- /dev/null
+++ b/util/spelling.txt
@@ -0,0 +1,1072 @@
+# Originally from Debian's Lintian tool. Various false positives have been
+# removed, and various additions have been made as they've been discovered
+# in the kernel source.
+#
+# License: GPLv2
+#
+# The format of each line is:
+# mistake||correction
+#
+abandonning||abandoning
+abigious||ambiguous
+abitrate||arbitrate
+abov||above
+abreviated||abbreviated
+absense||absence
+absolut||absolute
+absoulte||absolute
+acccess||access
+acceleratoin||acceleration
+accelleration||acceleration
+accesing||accessing
+accesnt||accent
+accessable||accessible
+accesss||access
+accidentaly||accidentally
+accidentually||accidentally
+accoding||according
+accomodate||accommodate
+accomodates||accommodates
+accordign||according
+accoring||according
+accout||account
+accquire||acquire
+accquired||acquired
+accross||across
+acessable||accessible
+acess||access
+achitecture||architecture
+acient||ancient
+acitions||actions
+acitve||active
+acknowldegement||acknowldegement
+acknowledgement||acknowledgment
+ackowledge||acknowledge
+ackowledged||acknowledged
+acording||according
+activete||activate
+acumulating||accumulating
+adapater||adapter
+addional||additional
+additionaly||additionally
+addres||address
+addreses||addresses
+addresss||address
+aditional||additional
+aditionally||additionally
+aditionaly||additionally
+adminstrative||administrative
+adress||address
+adresses||addresses
+adviced||advised
+afecting||affecting
+agaist||against
+albumns||albums
+alegorical||allegorical
+algorith||algorithm
+algorithmical||algorithmically
+algoritm||algorithm
+algoritms||algorithms
+algorrithm||algorithm
+algorritm||algorithm
+allign||align
+allocatrd||allocated
+allocte||allocate
+allpication||application
+alocate||allocate
+alogirhtms||algorithms
+alogrithm||algorithm
+alot||a lot
+alow||allow
+alows||allows
+altough||although
+alue||value
+ambigious||ambiguous
+amoung||among
+amout||amount
+analysator||analyzer
+ang||and
+anniversery||anniversary
+annoucement||announcement
+anomolies||anomalies
+anomoly||anomaly
+anway||anyway
+aplication||application
+appearence||appearance
+applicaion||application
+appliction||application
+applictions||applications
+appplications||applications
+appropiate||appropriate
+appropriatly||appropriately
+approriate||appropriate
+approriately||appropriately
+apropriate||appropriate
+aquainted||acquainted
+aquired||acquired
+aquisition||acquisition
+arbitary||arbitrary
+architechture||architecture
+arguement||argument
+arguements||arguments
+aritmetic||arithmetic
+arne't||aren't
+arraival||arrival
+artifical||artificial
+artillary||artillery
+asign||assign
+assertation||assertion
+assiged||assigned
+assigment||assignment
+assigments||assignments
+assistent||assistant
+assocation||association
+associcated||associated
+assotiated||associated
+assum||assume
+assumtpion||assumption
+asuming||assuming
+asycronous||asynchronous
+asynchnous||asynchronous
+atomatically||automatically
+atomicly||atomically
+attachement||attachment
+attched||attached
+attemps||attempts
+attruibutes||attributes
+authentification||authentication
+automaticaly||automatically
+automaticly||automatically
+automatize||automate
+automatized||automated
+automatizes||automates
+autonymous||autonomous
+auxillary||auxiliary
+auxilliary||auxiliary
+avaiable||available
+avaible||available
+availabe||available
+availabled||available
+availablity||availability
+availale||available
+availavility||availability
+availble||available
+availiable||available
+avalable||available
+avaliable||available
+aysnc||async
+backgroud||background
+backword||backward
+backwords||backwards
+bahavior||behavior
+bakup||backup
+baloon||balloon
+baloons||balloons
+bandwith||bandwidth
+batery||battery
+beacuse||because
+becasue||because
+becomming||becoming
+becuase||because
+beeing||being
+befor||before
+begining||beginning
+beter||better
+betweeen||between
+bianries||binaries
+bitmast||bitmask
+boardcast||broadcast
+borad||board
+boundry||boundary
+brievely||briefly
+broadcat||broadcast
+cacluated||calculated
+caculation||calculation
+calender||calendar
+calle||called
+calucate||calculate
+calulate||calculate
+cancelation||cancellation
+capabilites||capabilities
+capabitilies||capabilities
+capatibilities||capabilities
+carefuly||carefully
+cariage||carriage
+catagory||category
+cehck||check
+challange||challenge
+challanges||challenges
+chanell||channel
+changable||changeable
+channle||channel
+channnel||channel
+charachter||character
+charachters||characters
+charactor||character
+charater||character
+charaters||characters
+charcter||character
+chcek||check
+chck||check
+checksuming||checksumming
+childern||children
+childs||children
+chiled||child
+chked||checked
+chnage||change
+chnages||changes
+chnnel||channel
+choosen||chosen
+chouse||chose
+circumvernt||circumvent
+claread||cleared
+clared||cleared
+closeing||closing
+clustred||clustered
+collapsable||collapsible
+colorfull||colorful
+comand||command
+comit||commit
+commerical||commercial
+comming||coming
+comminucation||communication
+commited||committed
+commiting||committing
+committ||commit
+commoditiy||commodity
+compability||compatibility
+compaibility||compatibility
+compatability||compatibility
+compatable||compatible
+compatibiliy||compatibility
+compatibilty||compatibility
+compatiblity||compatibility
+competion||completion
+compilant||compliant
+compleatly||completely
+completly||completely
+complient||compliant
+componnents||components
+compres||compress
+compresion||compression
+comression||compression
+comunication||communication
+conbination||combination
+conditionaly||conditionally
+conected||connected
+configuratoin||configuration
+configuraton||configuration
+configuretion||configuration
+conider||consider
+conjuction||conjunction
+connectinos||connections
+connnection||connection
+connnections||connections
+consistancy||consistency
+consistant||consistent
+containes||contains
+containts||contains
+contaisn||contains
+contant||contact
+contence||contents
+continous||continuous
+continously||continuously
+continueing||continuing
+contraints||constraints
+controled||controlled
+controler||controller
+controll||control
+contruction||construction
+contry||country
+convertion||conversion
+convertor||converter
+convienient||convenient
+convinient||convenient
+corected||corrected
+correponding||corresponding
+correponds||corresponds
+correspoding||corresponding
+cotrol||control
+couter||counter
+coutner||counter
+cryptocraphic||cryptographic
+cunter||counter
+curently||currently
+dafault||default
+deafult||default
+deamon||daemon
+decompres||decompress
+decription||description
+defailt||default
+defferred||deferred
+definate||definite
+definately||definitely
+defintion||definition
+defintions||definitions
+defualt||default
+defult||default
+deivce||device
+delared||declared
+delare||declare
+delares||declares
+delaring||declaring
+delemiter||delimiter
+dependancies||dependencies
+dependancy||dependency
+dependant||dependent
+depreacted||deprecated
+depreacte||deprecate
+desactivate||deactivate
+desciptors||descriptors
+descripton||description
+descrition||description
+descritptor||descriptor
+desctiptor||descriptor
+desriptor||descriptor
+desriptors||descriptors
+destory||destroy
+destoryed||destroyed
+destorys||destroys
+destroied||destroyed
+detabase||database
+develope||develop
+developement||development
+developped||developed
+developpement||development
+developper||developer
+developpment||development
+deveolpment||development
+devided||divided
+deviece||device
+diable||disable
+dictionnary||dictionary
+didnt||didn't
+diferent||different
+differrence||difference
+difinition||definition
+diplay||display
+direectly||directly
+disapear||disappear
+disapeared||disappeared
+disappared||disappeared
+disconnet||disconnect
+discontinous||discontinuous
+dispertion||dispersion
+dissapears||disappears
+distiction||distinction
+docuentation||documentation
+documantation||documentation
+documentaion||documentation
+documment||document
+doesnt||doesn't
+dorp||drop
+dosen||doesn
+downlad||download
+downlads||downloads
+druing||during
+dynmaic||dynamic
+easilly||easily
+ecspecially||especially
+edditable||editable
+editting||editing
+efficently||efficiently
+ehther||ether
+eigth||eight
+eletronic||electronic
+enabledi||enabled
+enchanced||enhanced
+encorporating||incorporating
+encrupted||encrypted
+encrypiton||encryption
+endianess||endianness
+enhaced||enhanced
+enlightnment||enlightenment
+enocded||encoded
+enterily||entirely
+enviroiment||environment
+enviroment||environment
+environement||environment
+environent||environment
+eqivalent||equivalent
+equiped||equipped
+equivelant||equivalent
+equivilant||equivalent
+eror||error
+estbalishment||establishment
+etsablishment||establishment
+etsbalishment||establishment
+excecutable||executable
+exceded||exceeded
+excellant||excellent
+existance||existence
+existant||existent
+exixt||exist
+exlcude||exclude
+exlcusive||exclusive
+exmaple||example
+expecially||especially
+explicite||explicit
+explicitely||explicitly
+explict||explicit
+explictly||explicitly
+expresion||expression
+exprimental||experimental
+extened||extended
+extensability||extensibility
+extention||extension
+extracter||extractor
+faild||failed
+faill||fail
+failue||failure
+failuer||failure
+faireness||fairness
+faliure||failure
+familar||familiar
+fatser||faster
+feauture||feature
+feautures||features
+fetaure||feature
+fetaures||features
+fileystem||filesystem
+fimware||firmware
+finanize||finalize
+findn||find
+finilizes||finalizes
+finsih||finish
+flusing||flushing
+folloing||following
+followign||following
+follwing||following
+forseeable||foreseeable
+forse||force
+fortan||fortran
+forwardig||forwarding
+framwork||framework
+frequncy||frequency
+frome||from
+fucntion||function
+fuction||function
+fuctions||functions
+funcion||function
+functionallity||functionality
+functionaly||functionally
+functionnality||functionality
+functonality||functionality
+funtion||function
+funtions||functions
+furthur||further
+futhermore||furthermore
+futrue||future
+gaurenteed||guaranteed
+generiously||generously
+genric||generic
+globel||global
+grabing||grabbing
+grahical||graphical
+grahpical||graphical
+grapic||graphic
+guage||gauge
+guarenteed||guaranteed
+guarentee||guarantee
+halfs||halves
+hander||handler
+handfull||handful
+hanled||handled
+happend||happened
+harware||hardware
+heirarchically||hierarchically
+helpfull||helpful
+hierachy||hierarchy
+hierarchie||hierarchy
+howver||however
+hsould||should
+hypter||hyper
+identidier||identifier
+imblance||imbalance
+immeadiately||immediately
+immedaite||immediate
+immediatelly||immediately
+immediatly||immediately
+immidiate||immediate
+impelentation||implementation
+impementated||implemented
+implemantation||implementation
+implemenation||implementation
+implementaiton||implementation
+implementated||implemented
+implemention||implementation
+implemetation||implementation
+implemntation||implementation
+implentation||implementation
+implmentation||implementation
+implmenting||implementing
+incomming||incoming
+incompatabilities||incompatibilities
+incompatable||incompatible
+inconsistant||inconsistent
+increas||increase
+incrment||increment
+indendation||indentation
+indended||intended
+independant||independent
+independantly||independently
+independed||independent
+indiate||indicate
+inexpect||inexpected
+infomation||information
+informatiom||information
+informations||information
+informtion||information
+infromation||information
+ingore||ignore
+inital||initial
+initalised||initialized
+initalise||initialize
+initalize||initialize
+initation||initiation
+initators||initiators
+initializiation||initialization
+initialzed||initialized
+initilization||initialization
+initilize||initialize
+inofficial||unofficial
+insititute||institute
+instal||install
+inteface||interface
+integreated||integrated
+integrety||integrity
+integrey||integrity
+intendet||intended
+intented||intended
+interanl||internal
+interchangable||interchangeable
+interferring||interfering
+interger||integer
+intermittant||intermittent
+internel||internal
+interoprability||interoperability
+interrface||interface
+interrrupt||interrupt
+interrup||interrupt
+interrups||interrupts
+interruptted||interrupted
+interupted||interrupted
+interupt||interrupt
+intial||initial
+intialized||initialized
+intialize||initialize
+intregral||integral
+intrrupt||interrupt
+intuative||intuitive
+invaid||invalid
+invalde||invald
+invalide||invalid
+invididual||individual
+invokation||invocation
+invokations||invocations
+irrelevent||irrelevant
+isnt||isn't
+isssue||issue
+itslef||itself
+jave||java
+jeffies||jiffies
+juse||just
+jus||just
+kown||known
+langage||language
+langauage||language
+langauge||language
+langugage||language
+lauch||launch
+layed||laid
+leightweight||lightweight
+lengh||length
+lenght||length
+lenth||length
+lesstiff||lesstif
+libaries||libraries
+libary||library
+librairies||libraries
+libraris||libraries
+licenceing||licencing
+loggging||logging
+loggin||login
+logile||logfile
+loosing||losing
+losted||lost
+machinary||machinery
+maintainance||maintenance
+maintainence||maintenance
+maintan||maintain
+makeing||making
+malplaced||misplaced
+malplace||misplace
+managable||manageable
+managment||management
+mangement||management
+manoeuvering||maneuvering
+mappping||mapping
+mathimatical||mathematical
+mathimatic||mathematic
+mathimatics||mathematics
+maxium||maximum
+mechamism||mechanism
+meetign||meeting
+ment||meant
+mergable||mergeable
+mesage||message
+messags||messages
+messgaes||messages
+messsage||message
+messsages||messages
+microprocesspr||microprocessor
+milliseonds||milliseconds
+minumum||minimum
+miscelleneous||miscellaneous
+misformed||malformed
+mispelled||misspelled
+mispelt||misspelt
+miximum||maximum
+mmnemonic||mnemonic
+mnay||many
+modulues||modules
+monochorome||monochrome
+monochromo||monochrome
+monocrome||monochrome
+mopdule||module
+mroe||more
+mulitplied||multiplied
+multidimensionnal||multidimensional
+multple||multiple
+mumber||number
+muticast||multicast
+mutiple||multiple
+mutli||multi
+nams||names
+navagating||navigating
+nead||need
+neccecary||necessary
+neccesary||necessary
+neccessary||necessary
+necesary||necessary
+negaive||negative
+negoitation||negotiation
+negotation||negotiation
+nerver||never
+nescessary||necessary
+nessessary||necessary
+noticable||noticeable
+notications||notifications
+notifed||notified
+numebr||number
+numner||number
+obtaion||obtain
+occassionally||occasionally
+occationally||occasionally
+occurance||occurrence
+occurances||occurrences
+occured||occurred
+occurence||occurrence
+occure||occurred
+occuring||occurring
+offet||offset
+omitt||omit
+ommiting||omitting
+ommitted||omitted
+onself||oneself
+ony||only
+operatione||operation
+opertaions||operations
+optionnal||optional
+optmizations||optimizations
+orientatied||orientated
+orientied||oriented
+otherise||otherwise
+ouput||output
+overaall||overall
+overhread||overhead
+overlaping||overlapping
+overriden||overridden
+overun||overrun
+pacakge||package
+pachage||package
+packacge||package
+packege||package
+packge||package
+packtes||packets
+pakage||package
+pallette||palette
+paln||plan
+paramameters||parameters
+paramater||parameter
+parametes||parameters
+parametised||parametrised
+paramter||parameter
+paramters||parameters
+particuarly||particularly
+particularily||particularly
+pased||passed
+passin||passing
+pathes||paths
+pecularities||peculiarities
+peformance||performance
+peice||piece
+pendantic||pedantic
+peprocessor||preprocessor
+perfoming||performing
+permissons||permissions
+peroid||period
+persistance||persistence
+persistant||persistent
+platfrom||platform
+plattform||platform
+pleaes||please
+ploting||plotting
+plugable||pluggable
+poinnter||pointer
+poiter||pointer
+posible||possible
+positon||position
+possibilites||possibilities
+powerfull||powerful
+preceeded||preceded
+preceeding||preceding
+preceed||precede
+precendence||precedence
+precission||precision
+preemptable||preemptible
+prefered||preferred
+prefferably||preferably
+premption||preemption
+prepaired||prepared
+pressre||pressure
+primative||primitive
+princliple||principle
+priorty||priority
+privilaged||privileged
+privilage||privilege
+priviledge||privilege
+priviledges||privileges
+probaly||probably
+procceed||proceed
+proccesors||processors
+procesed||processed
+proces||process
+processessing||processing
+processess||processes
+processpr||processor
+processsed||processed
+processsing||processing
+procteted||protected
+prodecure||procedure
+progams||programs
+progess||progress
+programers||programmers
+programm||program
+programms||programs
+progresss||progress
+promiscous||promiscuous
+promps||prompts
+pronnounced||pronounced
+prononciation||pronunciation
+pronouce||pronounce
+pronunce||pronounce
+propery||property
+propigate||propagate
+propigation||propagation
+propogate||propagate
+prosess||process
+protable||portable
+protcol||protocol
+protecion||protection
+protocoll||protocol
+psudo||pseudo
+psuedo||pseudo
+psychadelic||psychedelic
+pwoer||power
+quering||querying
+raoming||roaming
+reasearcher||researcher
+reasearchers||researchers
+reasearch||research
+recepient||recipient
+receving||receiving
+recieved||received
+recieve||receive
+reciever||receiver
+recieves||receives
+recogniced||recognised
+recognizeable||recognizable
+recommanded||recommended
+recyle||recycle
+redircet||redirect
+redirectrion||redirection
+refcounf||refcount
+refence||reference
+refered||referred
+referenace||reference
+refering||referring
+refernces||references
+refernnce||reference
+refrence||reference
+registerd||registered
+registeresd||registered
+registes||registers
+registraration||registration
+regster||register
+regualar||regular
+reguator||regulator
+regulamentations||regulations
+reigstration||registration
+releated||related
+relevent||relevant
+remoote||remote
+remore||remote
+removeable||removable
+repectively||respectively
+replacable||replaceable
+replacments||replacements
+replys||replies
+reponse||response
+representaion||representation
+reqeust||request
+requiere||require
+requirment||requirement
+requred||required
+requried||required
+requst||request
+reseting||resetting
+resizeable||resizable
+resouces||resources
+resoures||resources
+responce||response
+ressizes||resizes
+ressource||resource
+ressources||resources
+retransmited||retransmitted
+retreived||retrieved
+retreive||retrieve
+retrive||retrieve
+retuned||returned
+reudce||reduce
+reuest||request
+reuqest||request
+reutnred||returned
+rmeoved||removed
+rmeove||remove
+rmeoves||removes
+rountine||routine
+routins||routines
+rquest||request
+runing||running
+runned||ran
+runnning||running
+runtine||runtime
+sacrifying||sacrificing
+safly||safely
+safty||safety
+savable||saveable
+scaned||scanned
+scaning||scanning
+scarch||search
+seach||search
+searchs||searches
+secquence||sequence
+secund||second
+segement||segment
+senarios||scenarios
+sentivite||sensitive
+separatly||separately
+sepcify||specify
+sepc||spec
+seperated||separated
+seperately||separately
+seperate||separate
+seperatly||separately
+seperator||separator
+sepperate||separate
+sequece||sequence
+sequencial||sequential
+serveral||several
+setts||sets
+settting||setting
+shotdown||shutdown
+shoud||should
+shouldnt||shouldn't
+shoule||should
+shrinked||shrunk
+siginificantly||significantly
+signabl||signal
+similary||similarly
+similiar||similar
+simlar||similar
+simliar||similar
+simpified||simplified
+singaled||signaled
+singal||signal
+singed||signed
+sleeped||slept
+softwares||software
+speach||speech
+specfic||specific
+speciefied||specified
+specifc||specific
+specifed||specified
+specificatin||specification
+specificaton||specification
+specifing||specifying
+specifiying||specifying
+speficied||specified
+speicify||specify
+speling||spelling
+spinlcok||spinlock
+spinock||spinlock
+splitted||split
+spreaded||spread
+sructure||structure
+stablilization||stabilization
+staically||statically
+staion||station
+standardss||standards
+standartization||standardization
+standart||standard
+staticly||statically
+stoped||stopped
+stoppped||stopped
+straming||streaming
+struc||struct
+structres||structures
+stuct||struct
+stucture||structure
+sturcture||structure
+subdirectoires||subdirectories
+suble||subtle
+substract||subtract
+succesfully||successfully
+succesful||successful
+successfull||successful
+sucessfully||successfully
+sucess||success
+superflous||superfluous
+superseeded||superseded
+suplied||supplied
+suported||supported
+suport||support
+suppored||supported
+supportin||supporting
+suppoted||supported
+suppported||supported
+suppport||support
+supress||suppress
+surpresses||suppresses
+susbsystem||subsystem
+suspicously||suspiciously
+swaping||swapping
+switchs||switches
+symetric||symmetric
+synax||syntax
+synchonized||synchronized
+syncronize||synchronize
+syncronizing||synchronizing
+syncronus||synchronous
+syste||system
+sytem||system
+sythesis||synthesis
+taht||that
+targetted||targeted
+targetting||targeting
+teh||the
+temorary||temporary
+temproarily||temporarily
+thier||their
+threds||threads
+threshhold||threshold
+throught||through
+thses||these
+tiggered||triggered
+tipically||typically
+tmis||this
+torerable||tolerable
+tramsmitted||transmitted
+tramsmit||transmit
+tranfer||transfer
+transciever||transceiver
+transferd||transferrd
+transfered||transferred
+transfering||transferring
+transision||transition
+transmittd||transmitted
+transormed||transformed
+trasmission||transmission
+treshold||threshold
+trigerring||triggering
+trun||turn
+ture||true
+tyep||type
+udpate||update
+uesd||used
+unconditionaly||unconditionally
+underun||underrun
+unecessary||unnecessary
+unexecpted||unexpected
+unexpectd||unexpected
+unexpeted||unexpected
+unfortunatelly||unfortunately
+unifiy||unify
+unintialized||uninitialized
+unknonw||unknown
+unknow||unknown
+unkown||unknown
+unneedingly||unnecessarily
+unresgister||unregister
+unsinged||unsigned
+unstabel||unstable
+unsuccessfull||unsuccessful
+unsuported||unsupported
+untill||until
+unuseful||useless
+upate||update
+usefule||useful
+usefull||useful
+usege||usage
+usera||users
+usualy||usually
+utilites||utilities
+utillities||utilities
+utilties||utilities
+utiltity||utility
+utitity||utility
+utitlty||utility
+vaid||valid
+vaild||valid
+valide||valid
+variantions||variations
+varient||variant
+vaule||value
+verbse||verbose
+verisons||versions
+verison||version
+verson||version
+vicefersa||vice-versa
+virtal||virtual
+virtaul||virtual
+virtiual||virtual
+visiters||visitors
+vitual||virtual
+wating||waiting
+wether||whether
+whataver||whatever
+whcih||which
+whenver||whenever
+wheter||whether
+whe||when
+wierd||weird
+wiil||will
+wirte||write
+withing||within
+wnat||want
+workarould||workaround
+writeing||writing
+writting||writing
+zombe||zombie
+zomebie||zombie