Files
threeeye bc1d038eac REVVL 6 Pro kernel bring-up: full working tree snapshot (2026-09-12)
Squashed backup commit (no shallow-clone history issues) of
xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream
commit 9eece662e) plus all fixes applied to get everpal_defconfig building
clean against a modern (GCC 15.2.0) toolchain -- see the previous commit
message on the lineage-23.2 branch (also in this repo history is not
available here since this is a squashed snapshot; full incremental history
lives in the local DL380 checkout and the upstream GitHub remote).

Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
2026-09-12 13:36:46 +00:00

1021 lines
26 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include "bt.h"
#include "btmtk_dbg_tp_evt_if.h"
#include <linux/pm_wakeup.h>
#include <linux/version.h>
#include <linux/pm_qos.h>
#include <linux/notifier.h>
#include <linux/fb.h>
#include <linux/suspend.h>
MODULE_LICENSE("Dual BSD/GPL");
/*******************************************************************************
* M A C R O S
********************************************************************************
*/
#define BT_DRIVER_NAME "mtk_stp_bt_chrdev"
#define BT_DEV_MAJOR 192
#define VERSION "2.0"
#define COMBO_IOC_MAGIC 0xb0
#define COMBO_IOCTL_FW_ASSERT _IOW(COMBO_IOC_MAGIC, 0, int)
#define COMBO_IOCTL_BT_SET_PSM _IOW(COMBO_IOC_MAGIC, 1, bool)
#define COMBO_IOCTL_BT_IC_HW_VER _IOR(COMBO_IOC_MAGIC, 2, void*)
#define COMBO_IOCTL_BT_IC_FW_VER _IOR(COMBO_IOC_MAGIC, 3, void*)
#define COMBO_IOCTL_BT_HOST_DEBUG _IOW(COMBO_IOC_MAGIC, 4, void*)
#define BT_BUFFER_SIZE 2048
#define FTRACE_STR_LOG_SIZE 256
#define REG_READL(addr) readl((volatile uint32_t *)(addr))
/*******************************************************************************
* P U B L I C D A T A
********************************************************************************
*/
/*******************************************************************************
* P R I V A T E D A T A
********************************************************************************
*/
static INT32 BT_devs = 1;
static INT32 BT_major = BT_DEV_MAJOR;
module_param(BT_major, uint, 0);
static struct cdev BT_cdev;
#if CREATE_NODE_DYNAMIC
static struct class *stpbt_class;
static struct device *stpbt_dev;
#endif
static UINT8 i_buf[BT_BUFFER_SIZE]; /* Input buffer for read */
static UINT8 o_buf[BT_BUFFER_SIZE]; /* Output buffer for write */
static struct semaphore wr_mtx, rd_mtx;
static struct wakeup_source *bt_wakelock;
/* Wait queue for poll and read */
static wait_queue_head_t inq;
static DECLARE_WAIT_QUEUE_HEAD(BT_wq);
static INT32 flag;
static INT32 bt_ftrace_flag;
static bool btonflag = 0;
UINT32 gBtDbgLevel = BT_LOG_INFO;
struct bt_dbg_st g_bt_dbg_st;
/* PM QOS feature*/
unsigned int pm_qos_support = 0;
static struct pm_qos_request qos_req;
static struct pm_qos_ctrl qos_ctrl;
/*
* Reset flag for whole chip reset scenario, to indicate reset status:
* 0 - normal, no whole chip reset occurs
* 1 - reset start
* 2 - reset end, have not sent Hardware Error event yet
* 3 - reset end, already sent Hardware Error event
*/
static UINT32 rstflag;
static UINT8 HCI_EVT_HW_ERROR[] = {0x04, 0x10, 0x01, 0x00};
static loff_t rd_offset;
extern bool g_bt_trace_pt;
extern int bt_dev_dbg_init(void);
extern int bt_dev_dbg_deinit(void);
extern int bt_dev_dbg_set_state(bool turn_on);
/*******************************************************************************
* bt host debug information for low power
********************************************************************************
*/
#define BTHOST_INFO_MAX 16
#define BTHOST_DESC_LEN 16
struct bthost_info{
uint32_t id; //0 for not used
char desc[BTHOST_DESC_LEN];
uint32_t value;
};
struct bthost_info bthost_info_table[BTHOST_INFO_MAX];
void bthost_debug_init(void)
{
uint32_t i = 0;
for (i = 0; i < BTHOST_INFO_MAX; i++){
bthost_info_table[i].id = 0;
bthost_info_table[i].desc[0] = '\0';
bthost_info_table[i].value = 0;
}
}
void bthost_debug_print(void)
{
uint32_t i = 0;
for (i = 0; i < BTHOST_INFO_MAX; i++){
if (bthost_info_table[i].id == 0){
BT_LOG_PRT_WARN("[bt host info][%d-%d] not set", i, BTHOST_INFO_MAX);
break;
}
else {
BT_LOG_PRT_WARN("[bt host info][%d][%s : 0x%08x]", i,
bthost_info_table[i].desc,
bthost_info_table[i].value);
}
}
}
void bthost_debug_save(uint32_t id, uint32_t value, char* desc)
{
uint32_t i = 0;
if (id == 0) {
BT_LOG_PRT_WARN("id (%d) must > 0\n", id);
return;
}
for (i = 0; i < BTHOST_INFO_MAX; i++){
// if the id is existed, save to the same column
if (bthost_info_table[i].id == id){
bthost_info_table[i].value = value;
return;
}
// save to the new column
if (bthost_info_table[i].id == 0){
bthost_info_table[i].id = id;
strncpy(bthost_info_table[i].desc, desc, BTHOST_DESC_LEN - 1);
bthost_info_table[i].value = value;
return;
}
}
BT_LOG_PRT_WARN("no space for %d\n", id);
}
/*******************************************************************************
* F U N C T I O N S
********************************************************************************
*/
static INT32 ftrace_print(const PINT8 str, ...)
{
#ifdef CONFIG_TRACING
va_list args;
int ret = 0;
INT8 temp_string[FTRACE_STR_LOG_SIZE];
if (bt_ftrace_flag) {
va_start(args, str);
ret = vsnprintf(temp_string, FTRACE_STR_LOG_SIZE, str, args);
va_end(args);
if (ret < 0) {
BT_LOG_PRT_ERR("error return value in vsnprintf ret = [%d]\n", ret);
return 0;
}
trace_printk("%s\n", temp_string);
}
#endif
return 0;
}
static size_t bt_report_hw_error(char *buf, size_t count, loff_t *f_pos)
{
size_t bytes_rest, bytes_read;
if (*f_pos == 0)
BT_LOG_PRT_INFO("Send Hardware Error event to stack to restart Bluetooth\n");
bytes_rest = sizeof(HCI_EVT_HW_ERROR) - *f_pos;
bytes_read = count < bytes_rest ? count : bytes_rest;
memcpy(buf, HCI_EVT_HW_ERROR + *f_pos, bytes_read);
*f_pos += bytes_read;
return bytes_read;
}
static uint32_t inline bt_read_cr(unsigned char *cr_name, uint32_t addr)
{
uint32_t value = 0;
uint8_t *base = ioremap(addr, 0x10);
if (base == NULL) {
BT_LOG_PRT_WARN("remapping 0x%08x fail\n", addr);
} else {
value = REG_READL(base);
iounmap(base);
BT_LOG_PRT_INFO("%s[0x%08x], read[0x%08x]\n", cr_name, addr, value);
}
return value;
}
/*
static struct notifier_block bt_fb_notifier;
static int bt_fb_notifier_callback(struct notifier_block
*self, unsigned long event, void *data)
{
struct fb_event *evdata = data;
int32_t blank = 0;
if ((event != FB_EVENT_BLANK))
return 0;
blank = *(int32_t *)evdata->data;
switch (blank) {
case FB_BLANK_UNBLANK:
case FB_BLANK_POWERDOWN:
if(btonflag == 1 && rstflag == 0) {
BT_LOG_PRT_INFO("blank state [%ld]", blank);
bt_read_cr("HOST_MAILBOX_BT_ADDR", 0x18007124);
}
break;
default:
break;
}
return 0;
}
static int bt_fb_notify_register(void)
{
int32_t ret;
bt_fb_notifier.notifier_call = bt_fb_notifier_callback;
ret = fb_register_client(&bt_fb_notifier);
if (ret)
BT_LOG_PRT_WARN("Register bt_fb_notifier failed:%d\n", ret);
else
BT_LOG_PRT_DBG("Register bt_fb_notifier succeed\n");
return ret;
}
static void bt_fb_notify_unregister(void)
{
fb_unregister_client(&bt_fb_notifier);
}
*/
static struct notifier_block bt_pm_notifier;
static int bt_pm_notifier_callback(struct notifier_block *nb,
unsigned long event, void *dummy)
{
BT_LOG_PRT_INFO("%s: btonflag[%d], event[%ld]", __func__, btonflag, event);
switch (event) {
case PM_SUSPEND_PREPARE:
case PM_POST_SUSPEND:
if(btonflag == 1 && rstflag == 0) {
// for fw debug power issue
bt_read_cr("HOST_MAILBOX_BT_ADDR", 0x18007124);
bthost_debug_print();
}
break;
default:
break;
}
return NOTIFY_DONE;
}
static int bt_pm_notify_register(void)
{
int32_t ret;
bt_pm_notifier.notifier_call = bt_pm_notifier_callback;
ret = register_pm_notifier(&bt_pm_notifier);
if (ret)
BT_LOG_PRT_ERR("Register bt_pm_notifier failed:%d\n", ret);
else
BT_LOG_PRT_INFO("Register bt_pm_notifier succeed\n");
return ret;
}
static void bt_pm_notify_unregister(void)
{
unregister_pm_notifier(&bt_pm_notifier);
}
/*******************************************************************
* WHOLE CHIP RESET message handler
********************************************************************
*/
static VOID bt_cdev_rst_cb(ENUM_WMTDRV_TYPE_T src,
ENUM_WMTDRV_TYPE_T dst, ENUM_WMTMSG_TYPE_T type, PVOID buf, UINT32 sz)
{
ENUM_WMTRSTMSG_TYPE_T rst_msg;
if (sz > sizeof(ENUM_WMTRSTMSG_TYPE_T)) {
BT_LOG_PRT_WARN("Invalid message format!\n");
return;
}
memcpy((PINT8)&rst_msg, (PINT8)buf, sz);
BT_LOG_PRT_DBG("src = %d, dst = %d, type = %d, buf = 0x%x sz = %d, max = %d\n",
src, dst, type, rst_msg, sz, WMTRSTMSG_RESET_MAX);
if ((src == WMTDRV_TYPE_WMT) && (dst == WMTDRV_TYPE_BT) && (type == WMTMSG_TYPE_RESET)) {
switch (rst_msg) {
case WMTRSTMSG_RESET_START:
#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
bt_state_notify(OFF);
#endif
BT_LOG_PRT_INFO("Whole chip reset start!\n");
rstflag = 1;
break;
case WMTRSTMSG_RESET_END:
case WMTRSTMSG_RESET_END_FAIL:
if (rst_msg == WMTRSTMSG_RESET_END)
BT_LOG_PRT_INFO("Whole chip reset end!\n");
else
BT_LOG_PRT_INFO("Whole chip reset fail!\n");
rd_offset = 0;
rstflag = 2;
flag = 1;
wake_up_interruptible(&inq);
wake_up(&BT_wq);
break;
default:
break;
}
}
}
static VOID BT_event_cb(VOID)
{
BT_LOG_PRT_DBG("BT_event_cb\n");
ftrace_print("%s get called", __func__);
/*
* Hold wakelock for 100ms to avoid system enter suspend in such case:
* FW has sent data to host, STP driver received the data and put it
* into BT rx queue, then send sleep command and release wakelock as
* quick sleep mechanism for low power, BT driver will wake up stack
* hci thread stuck in poll or read.
* But before hci thread comes to read data, system enter suspend,
* hci command timeout timer keeps counting during suspend period till
* expires, then the RTC interrupt wakes up system, command timeout
* handler is executed and meanwhile the event is received.
* This will false trigger FW assert and should never happen.
*/
__pm_wakeup_event(bt_wakelock, 100);
if(pm_qos_support) {
/* pm qos control:
* Set pm_qos to higher level for mass data transfer.
* When rx packet reveived, schedule a work to restore pm_qos setting after 500ms.
* If next packet is receiving before 500ms, this work will be cancel & re-schedule.
* (500ms: better power performance after experiment)
*/
down(&qos_ctrl.sem);
if(qos_ctrl.task != NULL ) {
cancel_delayed_work(&qos_ctrl.work);
if(qos_ctrl.is_hold == FALSE) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_update_request(&qos_req, 1000);
#else
cpu_latency_qos_update_request(&qos_req, 1000);
#endif
qos_ctrl.is_hold = TRUE;
BT_LOG_PRT_INFO("[qos] is_hold[%d]\n", qos_ctrl.is_hold);
}
queue_delayed_work(qos_ctrl.task, &qos_ctrl.work, (500 * HZ) >> 10);
}
up(&qos_ctrl.sem);
}
/*
* Finally, wake up any reader blocked in poll or read
*/
flag = 1;
wake_up_interruptible(&inq);
wake_up(&BT_wq);
ftrace_print("%s wake_up triggered", __func__);
}
unsigned int BT_poll(struct file *filp, poll_table *wait)
{
UINT32 mask = 0;
//bt_dbg_tp_evt(TP_ACT_POLL, 0, 0, NULL);
if ((mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0) ||
(rstflag == 1) || (rstflag == 3)) {
/*
* BT rx queue is empty, or whole chip reset start, or already sent Hardware Error event
* for whole chip reset end, add to wait queue.
*/
poll_wait(filp, &inq, wait);
/*
* Check if condition changes before poll_wait return, in case of
* wake_up_interruptible is called before add_wait_queue, otherwise,
* do_poll will get into sleep and never be waken up until timeout.
*/
if (!((mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0) ||
(rstflag == 1) || (rstflag == 3)))
mask |= POLLIN | POLLRDNORM; /* Readable */
} else {
/* BT rx queue has valid data, or whole chip reset end, have not sent Hardware Error event yet */
mask |= POLLIN | POLLRDNORM; /* Readable */
}
/* Do we need condition here? */
mask |= POLLOUT | POLLWRNORM; /* Writable */
ftrace_print("%s: return mask = 0x%04x", __func__, mask);
return mask;
}
static ssize_t __bt_write(const PUINT8 buffer, size_t count)
{
INT32 retval = 0;
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_WR_IN, 0, count, buffer);
retval = mtk_wcn_stp_send_data(buffer, count, BT_TASK_INDX);
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_WR_OUT, 0, count, buffer);
if (retval < 0)
BT_LOG_PRT_ERR("mtk_wcn_stp_send_data fail, retval %d\n", retval);
else if (retval == 0) {
/* Device cannot process data in time, STP queue is full and no space is available for write,
* native program should not call writev with no delay.
*/
BT_LOG_PRT_INFO_RATELIMITED("write count %zd, sent bytes %d, no space is available!\n", count, retval);
retval = -EAGAIN;
} else
BT_LOG_PRT_DBG("write count %zd, sent bytes %d\n", count, retval);
return retval;
}
ssize_t send_hci_frame(const PUINT8 buff, size_t count)
{
ssize_t retval = 0;
int retry = 0;
down(&wr_mtx);
do {
if (retry > 0) {
msleep(30);
BT_LOG_PRT_ERR("Send hci cmd failed, retry %d time(s)\n", retry);
}
retval = __bt_write(buff, count);
retry++;
} while (retval == -EAGAIN && retry < 3);
up(&wr_mtx);
return retval;
}
ssize_t BT_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
INT32 retval = 0;
size_t count = iov_iter_count(from);
ftrace_print("%s get called, count %zu", __func__, count);
down(&wr_mtx);
BT_LOG_PRT_DBG("count %zd\n", count);
if (rstflag) {
BT_LOG_PRT_ERR("whole chip reset occurs! rstflag=%d\n", rstflag);
retval = -EIO;
goto OUT;
}
if (count > 0) {
if (count > BT_BUFFER_SIZE) {
BT_LOG_PRT_ERR("write count %zd exceeds max buffer size %d", count, BT_BUFFER_SIZE);
retval = -EINVAL;
goto OUT;
}
if (copy_from_iter(o_buf, count, from) != count) {
retval = -EFAULT;
goto OUT;
}
BT_LOG_PRT_DBG_RAW(o_buf, count, "%s: len[%d], TX: ", __func__, count);
retval = __bt_write(o_buf, count);
}
OUT:
up(&wr_mtx);
return retval;
}
ssize_t BT_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
INT32 retval = 0;
ftrace_print("%s get called, count %zu", __func__, count);
down(&wr_mtx);
BT_LOG_PRT_DBG("count %zd pos %lld\n", count, *f_pos);
if (rstflag) {
BT_LOG_PRT_ERR("whole chip reset occurs! rstflag=%d\n", rstflag);
retval = -EIO;
goto OUT;
}
if (count > 0) {
if (count > BT_BUFFER_SIZE) {
BT_LOG_PRT_ERR("write count %zd exceeds max buffer size %d", count, BT_BUFFER_SIZE);
retval = -EINVAL;
goto OUT;
}
if (copy_from_user(o_buf, buf, count)) {
retval = -EFAULT;
goto OUT;
}
BT_LOG_PRT_DBG_RAW(o_buf, count, "%s: len[%d], TX: ", __func__, count);
retval = __bt_write(o_buf, count);
}
OUT:
up(&wr_mtx);
return retval;
}
ssize_t BT_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
INT32 retval = 0;
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_RD_IN, 0, count, NULL);
ftrace_print("%s get called, count %zu", __func__, count);
down(&rd_mtx);
BT_LOG_PRT_DBG("count %zd pos %lld\n", count, *f_pos);
if (rstflag) {
while (rstflag != 2) {
/*
* If nonblocking mode, return directly.
* O_NONBLOCK is specified during open()
*/
if (filp->f_flags & O_NONBLOCK) {
BT_LOG_PRT_ERR("Non-blocking read, whole chip reset occurs! rstflag=%d\n", rstflag);
retval = -EAGAIN;
goto OUT;
}
wait_event(BT_wq, flag != 0);
flag = 0;
}
/*
* Reset end, send Hardware Error event to stack only once.
* To avoid high frequency read from stack before process is killed, set rstflag to 3
* to block poll and read after Hardware Error event is sent.
*/
retval = bt_report_hw_error(i_buf, count, &rd_offset);
if (rd_offset == sizeof(HCI_EVT_HW_ERROR)) {
rd_offset = 0;
rstflag = 3;
}
if (copy_to_user(buf, i_buf, retval)) {
retval = -EFAULT;
if (rstflag == 3)
rstflag = 2;
}
goto OUT;
}
if (count > BT_BUFFER_SIZE) {
count = BT_BUFFER_SIZE;
BT_LOG_PRT_WARN("Shorten read count from %zd to %d\n", count, BT_BUFFER_SIZE);
}
do {
retval = mtk_wcn_stp_receive_data(i_buf, count, BT_TASK_INDX);
if (retval < 0) {
BT_LOG_PRT_ERR("mtk_wcn_stp_receive_data fail, retval %d\n", retval);
goto OUT;
} else if (retval == 0) { /* Got nothing, wait for STP's signal */
/*
* If nonblocking mode, return directly.
* O_NONBLOCK is specified during open()
*/
if (filp->f_flags & O_NONBLOCK) {
BT_LOG_PRT_ERR("Non-blocking read, no data is available!\n");
retval = -EAGAIN;
goto OUT;
}
wait_event(BT_wq, flag != 0);
flag = 0;
} else { /* Got something from STP driver */
// for bt_dbg user trx function
if (g_bt_dbg_st.trx_enable) {
g_bt_dbg_st.trx_cb(i_buf, retval);
}
//BT_LOG_PRT_DBG("Read bytes %d\n", retval);
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_RD_OUT, 0, retval, i_buf);
BT_LOG_PRT_DBG_RAW(i_buf, retval, "%s: len[%d], RX: ", __func__, retval);
break;
}
} while (!mtk_wcn_stp_is_rxqueue_empty(BT_TASK_INDX) && rstflag == 0);
if (retval == 0) {
if (rstflag != 2) { /* Should never happen */
WARN(1, "Blocking read is waken up with no data but rstflag=%d\n", rstflag);
retval = -EIO;
goto OUT;
} else { /* Reset end, send Hardware Error event only once */
retval = bt_report_hw_error(i_buf, count, &rd_offset);
if (rd_offset == sizeof(HCI_EVT_HW_ERROR)) {
rd_offset = 0;
rstflag = 3;
}
}
}
if (copy_to_user(buf, i_buf, retval)) {
retval = -EFAULT;
if (rstflag == 3)
rstflag = 2;
}
OUT:
up(&rd_mtx);
return retval;
}
/* int BT_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) */
long BT_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
INT32 retval = 0;
UINT32 reason;
UINT32 ver = 0;
uint8_t host_dbg_buff[32]; //arg: id[0:3], value[4:7], desc[8:31]
BT_LOG_PRT_DBG("cmd: 0x%08x\n", cmd);
switch (cmd) {
case COMBO_IOCTL_FW_ASSERT:
/* Trigger FW assert for debug */
reason = (UINT32)arg & 0xFFFF;
BT_LOG_PRT_INFO("Host trigger FW assert......, reason:%d\n", reason);
if (reason == 31) /* HCI command timeout */
BT_LOG_PRT_INFO("HCI command timeout OpCode 0x%04x\n", ((UINT32)arg >> 16) & 0xFFFF);
if (mtk_wcn_wmt_assert(WMTDRV_TYPE_BT, reason) == MTK_WCN_BOOL_TRUE) {
BT_LOG_PRT_INFO("Host trigger FW assert succeed\n");
retval = 0;
} else {
BT_LOG_PRT_ERR("Host trigger FW assert failed\n");
retval = -EBUSY;
}
break;
case COMBO_IOCTL_BT_SET_PSM:
/* BT stack may need to dynamically enable/disable Power Saving Mode
* in some scenarios for performance, e.g. A2DP chopping.
*/
BT_LOG_PRT_INFO("BT stack change PSM setting: %lu\n", arg);
retval = mtk_wcn_wmt_psm_ctrl((MTK_WCN_BOOL)arg);
break;
case COMBO_IOCTL_BT_IC_HW_VER:
ver = mtk_wcn_wmt_ic_info_get(WMTCHIN_HWVER);
BT_LOG_PRT_INFO("HW ver: 0x%x\n", ver);
if (copy_to_user((UINT32 __user *)arg, &ver, sizeof(ver)))
retval = -EFAULT;
break;
case COMBO_IOCTL_BT_IC_FW_VER:
ver = mtk_wcn_wmt_ic_info_get(WMTCHIN_FWVER);
BT_LOG_PRT_INFO("FW ver: 0x%x\n", ver);
if (copy_to_user((UINT32 __user *)arg, &ver, sizeof(ver)))
retval = -EFAULT;
break;
case COMBO_IOCTL_BT_HOST_DEBUG:
if (copy_from_user(host_dbg_buff, (uint8_t __user*)arg, 32))
retval = -EFAULT;
else {
uint32_t* pint32 = (uint32_t*)&host_dbg_buff[0];
BT_LOG_PRT_INFO("id[%x], value[0x%08x], desc[%s]", pint32[0], pint32[1], &host_dbg_buff[8]);
bthost_debug_save(pint32[0], pint32[1], (char*)&host_dbg_buff[8]);
}
break;
default:
BT_LOG_PRT_ERR("Unknown cmd: 0x%08x\n", cmd);
retval = -EOPNOTSUPP;
break;
}
return retval;
}
long BT_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
return BT_unlocked_ioctl(filp, cmd, arg);
}
static void pm_qos_set_feature(void)
{
#define DTS_QOS_KEY "pm_qos_support"
int rc = 0;
struct device_node *node = NULL;
node = of_find_compatible_node(NULL, NULL, "mediatek,bt");
if (node) {
rc = of_property_read_u32(node, DTS_QOS_KEY, &pm_qos_support);
if (!rc)
BT_LOG_PRT_ERR("get property[%s] fail!\n", DTS_QOS_KEY);
} else
BT_LOG_PRT_ERR("get dts[mediatek,bt] fail!\n");
BT_LOG_PRT_INFO("property[%s] = %d\n", DTS_QOS_KEY, pm_qos_support);
}
static void pm_qos_release(struct work_struct *pwork)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#else
cpu_latency_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#endif
qos_ctrl.is_hold = FALSE;
BT_LOG_PRT_INFO("[qos] is_hold[%d]\n", qos_ctrl.is_hold);
}
static int BT_open(struct inode *inode, struct file *file)
{
if(btonflag) {
BT_LOG_PRT_WARN("BT already on!\n");
return -EIO;
}
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_PWR_ON, 0, 0, NULL);
BT_LOG_PRT_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
/* Turn on BT */
if (mtk_wcn_wmt_func_on(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
BT_LOG_PRT_WARN("WMT turn on BT fail!\n");
return -EIO;
}
BT_LOG_PRT_INFO("WMT turn on BT OK!\n");
if (mtk_wcn_stp_is_ready() == MTK_WCN_BOOL_FALSE) {
BT_LOG_PRT_ERR("STP is not ready!\n");
mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT);
return -EIO;
}
mtk_wcn_stp_set_bluez(0);
BT_LOG_PRT_INFO("Now it's in MTK Bluetooth Mode\n");
BT_LOG_PRT_INFO("STP is ready!\n");
BT_LOG_PRT_DBG("Register BT event callback!\n");
mtk_wcn_stp_register_event_cb(BT_TASK_INDX, BT_event_cb);
BT_LOG_PRT_DBG("Register BT reset callback!\n");
mtk_wcn_wmt_msgcb_reg(WMTDRV_TYPE_BT, bt_cdev_rst_cb);
rstflag = 0;
bt_ftrace_flag = 1;
btonflag = 1;
sema_init(&wr_mtx, 1);
sema_init(&rd_mtx, 1);
#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
bt_state_notify(ON);
#endif
bt_dev_dbg_set_state(TRUE);
if(pm_qos_support) {
down(&qos_ctrl.sem);
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#else
cpu_latency_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#endif
qos_ctrl.is_hold = FALSE;
qos_ctrl.task = create_singlethread_workqueue("pm_qos_task");
if (!qos_ctrl.task){
BT_LOG_PRT_ERR("fail to create pm_qos_task");
return -EIO;
}
INIT_DELAYED_WORK(&qos_ctrl.work, pm_qos_release);
up(&qos_ctrl.sem);
}
bt_pm_notify_register();
bthost_debug_init();
return 0;
}
static int BT_close(struct inode *inode, struct file *file)
{
BT_LOG_PRT_INFO("major %d minor %d (pid %d)\n", imajor(inode), iminor(inode), current->pid);
if (g_bt_trace_pt)
bt_dbg_tp_evt(TP_ACT_PWR_OFF, 0, 0, NULL);
bthost_debug_init();
bt_pm_notify_unregister();
bt_dev_dbg_set_state(FALSE);
#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
bt_state_notify(OFF);
#endif
rstflag = 0;
bt_ftrace_flag = 0;
btonflag = 0;
mtk_wcn_wmt_msgcb_unreg(WMTDRV_TYPE_BT);
mtk_wcn_stp_register_event_cb(BT_TASK_INDX, NULL);
if(pm_qos_support) {
down(&qos_ctrl.sem);
if(qos_ctrl.task != NULL) {
BT_LOG_PRT_INFO("[qos] cancel delayed work\n");
cancel_delayed_work(&qos_ctrl.work);
flush_workqueue(qos_ctrl.task);
destroy_workqueue(qos_ctrl.task);
qos_ctrl.task = NULL;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#else
cpu_latency_qos_update_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#endif
qos_ctrl.is_hold = FALSE;
up(&qos_ctrl.sem);
}
if (mtk_wcn_wmt_func_off(WMTDRV_TYPE_BT) == MTK_WCN_BOOL_FALSE) {
BT_LOG_PRT_ERR("WMT turn off BT fail!\n");
return -EIO; /* Mostly, native program will not check this return value. */
}
BT_LOG_PRT_INFO("WMT turn off BT OK!\n");
return 0;
}
const struct file_operations BT_fops = {
.open = BT_open,
.release = BT_close,
.read = BT_read,
.write = BT_write,
.write_iter = BT_write_iter,
/* .ioctl = BT_ioctl, */
.unlocked_ioctl = BT_unlocked_ioctl,
.compat_ioctl = BT_compat_ioctl,
.poll = BT_poll
};
static int BT_init(void)
{
dev_t dev;
INT32 alloc_ret = 0;
INT32 cdv_err = 0;
dev = MKDEV(BT_major, 0);
/* Initialize wait queue */
init_waitqueue_head(&(inq));
/* Initialize wake lock */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 149)
BT_LOG_PRT_INFO("wakeup_source_register() with kernel-4.14.149\n");
bt_wakelock = wakeup_source_register(NULL, "bt_drv");
#else
bt_wakelock = wakeup_source_register("bt_drv");
#endif
if(!bt_wakelock) {
BT_LOG_PRT_ERR("%s: init bt_wakelock failed!\n", __func__);
}
/* Allocate char device */
alloc_ret = register_chrdev_region(dev, BT_devs, BT_DRIVER_NAME);
if (alloc_ret) {
BT_LOG_PRT_ERR("Failed to register device numbers\n");
return alloc_ret;
}
cdev_init(&BT_cdev, &BT_fops);
BT_cdev.owner = THIS_MODULE;
cdv_err = cdev_add(&BT_cdev, dev, BT_devs);
if (cdv_err)
goto error;
#if CREATE_NODE_DYNAMIC /* mknod replace */
stpbt_class = class_create(THIS_MODULE, "stpbt");
if (IS_ERR(stpbt_class))
goto error;
stpbt_dev = device_create(stpbt_class, NULL, dev, NULL, "stpbt");
if (IS_ERR(stpbt_dev))
goto error;
#endif
BT_LOG_PRT_INFO("%s driver(major %d) installed\n", BT_DRIVER_NAME, BT_major);
#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
fw_log_bt_init();
#endif
bt_dev_dbg_init();
pm_qos_set_feature();
if(pm_qos_support) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_add_request(&qos_req, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
#else
cpu_latency_qos_add_request(&qos_req, PM_QOS_DEFAULT_VALUE);
#endif
sema_init(&qos_ctrl.sem, 1);
}
return 0;
error:
#if CREATE_NODE_DYNAMIC
if (stpbt_dev && !IS_ERR(stpbt_dev)) {
device_destroy(stpbt_class, dev);
stpbt_dev = NULL;
}
if (stpbt_class && !IS_ERR(stpbt_class)) {
class_destroy(stpbt_class);
stpbt_class = NULL;
}
#endif
if (cdv_err == 0)
cdev_del(&BT_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, BT_devs);
return -1;
}
static void BT_exit(void)
{
dev_t dev;
if(pm_qos_support) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
pm_qos_remove_request(&qos_req);
#else
cpu_latency_qos_remove_request(&qos_req);
#endif
}
bt_dev_dbg_deinit();
#ifdef CONFIG_MTK_CONNSYS_DEDICATED_LOG_PATH
fw_log_bt_exit();
#endif
dev = MKDEV(BT_major, 0);
/* Destroy wake lock*/
wakeup_source_unregister(bt_wakelock);
#if CREATE_NODE_DYNAMIC
if (stpbt_dev && !IS_ERR(stpbt_dev)) {
device_destroy(stpbt_class, dev);
stpbt_dev = NULL;
}
if (stpbt_class && !IS_ERR(stpbt_class)) {
class_destroy(stpbt_class);
stpbt_class = NULL;
}
#endif
cdev_del(&BT_cdev);
unregister_chrdev_region(dev, BT_devs);
BT_LOG_PRT_INFO("%s driver removed\n", BT_DRIVER_NAME);
}
#ifdef MTK_WCN_REMOVE_KERNEL_MODULE
int mtk_wcn_stpbt_drv_init(void)
{
return BT_init();
}
EXPORT_SYMBOL(mtk_wcn_stpbt_drv_init);
void mtk_wcn_stpbt_drv_exit(void)
{
return BT_exit();
}
EXPORT_SYMBOL(mtk_wcn_stpbt_drv_exit);
#else
module_init(BT_init);
module_exit(BT_exit);
#endif