| /* |
| * Copyright (c) 2016 MediaTek Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * See http://www.gnu.org/licenses/gpl-2.0.html for more details. |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <net/bluetooth/bluetooth.h> |
| #include <net/bluetooth/hci_core.h> |
| #if (KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE) |
| #include <linux/sched.h> |
| #else |
| #include <uapi/linux/sched/types.h> |
| #endif |
| |
| #include "btmtk_define.h" |
| #include "btmtk_drv.h" |
| #include "btmtk_sdio.h" |
| |
| /* |
| * This function is called by interface specific interrupt handler. |
| * It updates Power Save & Host Sleep states, and wakes up the main |
| * thread. |
| */ |
| void btmtk_interrupt(struct btmtk_private *priv) |
| { |
| priv->adapter->int_count++; |
| |
| wake_up_interruptible(&priv->main_thread.wait_q); |
| } |
| EXPORT_SYMBOL_GPL(btmtk_interrupt); |
| |
| static int btmtk_tx_pkt(struct btmtk_private *priv, struct sk_buff *skb) |
| { |
| int ret = 0; |
| u32 sdio_header_len = 0; |
| |
| if (!skb) { |
| BTMTK_WARN("skb is NULL return -EINVAL"); |
| return -EINVAL; |
| } |
| |
| BTMTK_DBG("skb->len %d", skb->len); |
| |
| if (!skb->data) { |
| BTMTK_WARN("skb->data is NULL return -EINVAL"); |
| return -EINVAL; |
| } |
| |
| if (!skb->len || ((skb->len + BTM_HEADER_LEN) > MTK_TXDATA_SIZE)) { |
| BTMTK_WARN("Tx Error: Bad skb length %d : %d", |
| skb->len, MTK_TXDATA_SIZE); |
| return -EINVAL; |
| } |
| |
| if (priv->hci_snoop_save) |
| priv->hci_snoop_save(bt_cb(skb)->pkt_type, skb->data, skb->len); |
| |
| sdio_header_len = skb->len + BTM_HEADER_LEN; |
| memset(txbuf, 0, MTK_TXDATA_SIZE); |
| txbuf[0] = (sdio_header_len & 0x0000ff); |
| txbuf[1] = (sdio_header_len & 0x00ff00) >> 8; |
| txbuf[2] = 0; |
| txbuf[3] = 0; |
| txbuf[4] = bt_cb(skb)->pkt_type; |
| memcpy(&txbuf[5], &skb->data[0], skb->len); |
| if (priv->hw_host_to_card) |
| ret = priv->hw_host_to_card(priv, txbuf, sdio_header_len); |
| |
| BTMTK_DBG("end"); |
| return ret; |
| } |
| |
| static void btmtk_init_adapter(struct btmtk_private *priv) |
| { |
| int buf_size; |
| |
| buf_size = ALIGN_SZ(SDIO_BLOCK_SIZE, BTSDIO_DMA_ALIGN); |
| priv->adapter->hw_regs_buf = kzalloc(buf_size, GFP_KERNEL); |
| if (!priv->adapter->hw_regs_buf) { |
| priv->adapter->hw_regs = NULL; |
| BTMTK_ERR("Unable to allocate buffer for h/w_regs."); |
| } else { |
| priv->adapter->hw_regs = |
| (u8 *)ALIGN_ADDR(priv->adapter->hw_regs_buf, |
| BTSDIO_DMA_ALIGN); |
| BTMTK_DBG("hw_regs_buf=%p hw_regs=%p", |
| priv->adapter->hw_regs_buf, priv->adapter->hw_regs); |
| } |
| } |
| |
| static void btmtk_free_adapter(struct btmtk_private *priv) |
| { |
| kfree(priv->adapter->hw_regs_buf); |
| } |
| |
| /* |
| * This function handles the event generated by firmware, rx data |
| * received from firmware, and tx data sent from kernel. |
| */ |
| |
| static int btmtk_service_main_thread(void *data) |
| { |
| struct btmtk_thread *thread = data; |
| struct btmtk_private *priv = thread->priv; |
| struct btmtk_adapter *adapter = NULL; |
| struct btmtk_sdio_card *card = NULL; |
| #if (KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE) |
| wait_queue_t wait; |
| #else |
| struct wait_queue_entry wait; |
| #endif |
| struct sk_buff *skb; |
| int ret = 0; |
| int i = 0; |
| ulong flags; |
| #if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE) |
| struct sched_param param = { .sched_priority = 90 };/*RR 90 is the same as audio*/ |
| #endif |
| int reset_flag = 0; |
| |
| #if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE) |
| sched_setscheduler(current, SCHED_RR, ¶m); |
| #else |
| sched_set_fifo(current); |
| #endif |
| for (i = 0; i <= 1000; i++) { |
| if (kthread_should_stop()) { |
| BTMTK_INFO("main_thread: break from main thread for probe_ready"); |
| break; |
| } |
| |
| if (probe_ready) |
| break; |
| |
| /* BTMTK_INFO("probe_ready %d delay 10ms~15ms", probe_ready);*/ |
| usleep_range(10*1000, 15*1000); |
| |
| if (i == 1000) { |
| BTMTK_WARN("probe_ready %d i = %d try too many times return", |
| probe_ready, i); |
| return 0; |
| } |
| } |
| |
| if (priv->adapter) |
| adapter = priv->adapter; |
| else { |
| BTMTK_ERR("priv->adapter is NULL return"); |
| return 0; |
| } |
| |
| if (priv->btmtk_dev.card) |
| card = priv->btmtk_dev.card; |
| else { |
| BTMTK_ERR("priv->btmtk_dev.card is NULL return"); |
| return 0; |
| } |
| |
| thread->thread_status = 1; |
| init_waitqueue_entry(&wait, current); |
| for (;;) { |
| add_wait_queue(&thread->wait_q, &wait); |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (kthread_should_stop()) { |
| remove_wait_queue(&thread->wait_q, &wait); |
| BTMTK_WARN("main_thread: break from main thread"); |
| break; |
| } |
| |
| if ((((!adapter->int_count) && |
| (!priv->btmtk_dev.tx_dnld_rdy || |
| skb_queue_empty(&card->tx_queue)))) && |
| (!priv->btmtk_dev.reset_dongle)) { |
| BTMTK_DBG("main_thread is sleeping..."); |
| schedule(); |
| } |
| |
| set_current_state(TASK_RUNNING); |
| |
| remove_wait_queue(&thread->wait_q, &wait); |
| |
| if (kthread_should_stop()) { |
| BTMTK_WARN("break after wake up"); |
| break; |
| } |
| |
| if (priv->btmtk_dev.reset_dongle) { |
| ret = priv->hw_sdio_reset_dongle(); |
| if (ret) { |
| BTMTK_ERR(L0_RESET_TAG "hw reset dongle error <%d>", ret); |
| } else { |
| BTMTK_INFO(L0_RESET_TAG "hw reset dongle done"); |
| reset_flag = 1; |
| break; |
| } |
| } |
| |
| if (priv->btmtk_dev.reset_progress) |
| continue; |
| |
| ret = priv->hw_set_own_back(DRIVER_OWN); |
| if (ret) { |
| BTMTK_ERR("set driver own return fail"); |
| priv->start_reset_dongle_progress(); |
| continue; |
| } |
| |
| spin_lock_irqsave(&priv->driver_lock, flags); |
| if (adapter->int_count) { |
| BTMTK_DBG("go int"); |
| adapter->int_count = 0; |
| spin_unlock_irqrestore(&priv->driver_lock, flags); |
| if (priv->hw_process_int_status(priv)) { |
| priv->start_reset_dongle_progress(); |
| continue; |
| } |
| } else { |
| BTMTK_DBG("go tx"); |
| spin_unlock_irqrestore(&priv->driver_lock, flags); |
| } |
| |
| if (!priv->btmtk_dev.tx_dnld_rdy) { |
| BTMTK_DBG("tx_dnld_rdy == 0, continue"); |
| continue; |
| } |
| |
| spin_lock_irqsave(&priv->driver_lock, flags); |
| skb = skb_dequeue(&card->tx_queue); |
| spin_unlock_irqrestore(&priv->driver_lock, flags); |
| |
| if (skb) { |
| if (skb->len < 16) |
| btmtk_print_buffer_conent(skb->data, skb->len); |
| else |
| btmtk_print_buffer_conent(skb->data, 16); |
| |
| ret = btmtk_tx_pkt(priv, skb); |
| if (ret && (ret != (-EINVAL))) { |
| BTMTK_ERR("tx pkt return fail %d", ret); |
| priv->start_reset_dongle_progress(); |
| continue; |
| } |
| |
| BTMTK_DBG("after btmtk_tx_pkt kfree_skb"); |
| kfree_skb(skb); |
| } |
| |
| if (skb_queue_empty(&card->tx_queue)) { |
| ret = priv->hw_set_own_back(FW_OWN); |
| if (ret) { |
| BTMTK_ERR("set fw own return fail"); |
| priv->start_reset_dongle_progress(); |
| continue; |
| } |
| } |
| } |
| BTMTK_WARN("end"); |
| thread->thread_status = 0; |
| |
| return 0; |
| } |
| |
| struct btmtk_private *btmtk_add_card(void *data) |
| { |
| struct btmtk_sdio_card *card = (struct btmtk_sdio_card *)data; |
| struct btmtk_private *priv; |
| |
| BTMTK_INFO("begin"); |
| |
| priv = card->priv; |
| |
| btmtk_init_adapter(priv); |
| |
| BTMTK_INFO("Starting kthread..."); |
| priv->main_thread.priv = priv; |
| spin_lock_init(&priv->driver_lock); |
| |
| init_waitqueue_head(&priv->main_thread.wait_q); |
| priv->main_thread.task = kthread_run(btmtk_service_main_thread, |
| &priv->main_thread, "btmtk_main_service"); |
| if (IS_ERR(priv->main_thread.task)) |
| goto err_thread; |
| |
| priv->btmtk_dev.card = card; |
| priv->btmtk_dev.tx_dnld_rdy = true; |
| |
| return priv; |
| |
| err_thread: |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(btmtk_add_card); |
| |
| int btmtk_remove_card(struct btmtk_private *priv) |
| { |
| BTMTK_INFO("begin, stop main_thread"); |
| if (!IS_ERR(priv->main_thread.task) && (priv->main_thread.thread_status)) { |
| kthread_stop(priv->main_thread.task); |
| BTMTK_INFO("wake_up_interruptible main_thread done"); |
| } |
| BTMTK_INFO("stop main_thread done"); |
| #ifdef CONFIG_DEBUG_FS |
| /*btmtk_debugfs_remove(hdev);*/ |
| #endif |
| |
| btmtk_free_adapter(priv); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(btmtk_remove_card); |
| |
| MODULE_AUTHOR("Mediatek Ltd."); |
| MODULE_DESCRIPTION("Mediatek Bluetooth driver ver " VERSION); |
| MODULE_VERSION(VERSION); |
| MODULE_LICENSE("GPL v2"); |