| // 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 |