blob: 1c6cc3d4e8bd262992e033fbb56eb13f4679ff25 [file] [log] [blame] [edit]
/*
* 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, &param);
#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");