1161 lines
30 KiB
C
1161 lines
30 KiB
C
/*
|
|
*
|
|
* Seekwave Bluetooth driver
|
|
*
|
|
* Copyright (C) 2023 Seekwave Tech Ltd.
|
|
*
|
|
*
|
|
* 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/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/platform_device.h>
|
|
#include <net/bluetooth/bluetooth.h>
|
|
#include <net/bluetooth/hci_core.h>
|
|
//#include <linux/platform_data/skw_platform_data.h>
|
|
#include <skw_platform_data.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/version.h>
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include "skw_btsnoop.h"
|
|
#include "skw_log.h"
|
|
#include "skw_common.h"
|
|
|
|
#define VERSION "0.1"
|
|
|
|
|
|
|
|
|
|
enum
|
|
{
|
|
BT_STATE_DEFAULT = 0x00,
|
|
BT_STATE_CLOSE,
|
|
BT_STATE_REMOVE
|
|
};
|
|
|
|
int skwbt_log_disable = 0;
|
|
int is_init_mode = 0;
|
|
uint16_t chip_version = 0;
|
|
static wait_queue_head_t nv_wait_queue;
|
|
static wait_queue_head_t recovery_wait_queue;
|
|
static wait_queue_head_t close_wait_queue;
|
|
Wakeup_ADV_Info_St wakeup_adv_info = {0};
|
|
char *bd_addr = NULL;
|
|
|
|
static atomic_t evt_recv;
|
|
static atomic_t cmd_reject;
|
|
static atomic_t atomic_close_sync;//make sure running close func before remove func
|
|
|
|
module_param(bd_addr, charp, S_IRUSR);
|
|
|
|
|
|
static int btseekwave_send_frame(struct hci_dev *hdev, struct sk_buff *skb);
|
|
int btseekwave_plt_event_notifier(struct notifier_block *nb, unsigned long action, void *param);
|
|
|
|
int btseekwave_send_hci_command(struct hci_dev *hdev, u16 opcode, int len, char *cmd_pld);
|
|
|
|
extern int skw_start_bt_service(void);
|
|
extern int skw_stop_bt_service(void);
|
|
struct btseekwave_data
|
|
{
|
|
struct hci_dev *hdev;
|
|
struct sv6160_platform_data *pdata;
|
|
|
|
struct work_struct work;
|
|
|
|
struct notifier_block plt_notifier;
|
|
uint8_t plt_notifier_set;
|
|
uint8_t bt_is_open;
|
|
|
|
struct sk_buff_head cmd_txq;
|
|
struct sk_buff_head data_txq;
|
|
struct sk_buff_head audio_txq;
|
|
};
|
|
|
|
struct btseekwave_data *skw_data = NULL;
|
|
|
|
|
|
void btseekwave_hci_hardware_error(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
int len = 3;
|
|
uint8_t hw_err_pkt[4] = {HCI_EVENT_PKT, HCI_EVT_HARDWARE_ERROR, 0x01, 0x00};
|
|
uint8_t *base_ptr = NULL;
|
|
skb = alloc_skb(len, GFP_ATOMIC);
|
|
if (!skb)
|
|
{
|
|
SKWBT_ERROR("%s: failed to allocate mem", __func__);
|
|
return;
|
|
}
|
|
base_ptr = (uint8_t *)skb_put(skb, len);
|
|
if(base_ptr)//for Coverity scan
|
|
{
|
|
memcpy(base_ptr, hw_err_pkt + 1, len);
|
|
}
|
|
bt_cb(skb)->pkt_type = HCI_EVENT_PKT;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
hci_recv_frame(hdev, skb);
|
|
#else
|
|
hci_recv_frame(skb);
|
|
#endif
|
|
|
|
SKWBT_INFO("%s enter", __func__);
|
|
}
|
|
|
|
|
|
static int btseekwave_tx_packet(int portno, struct btseekwave_data *data, struct sk_buff *skb)
|
|
{
|
|
int err = 0;
|
|
u32 *d;
|
|
uint32_t pkt_len = skb->len;
|
|
|
|
d = (u32 *)skb->data;
|
|
|
|
//SKWBT_INFO("%s enter size %d: 0x%x 0x%x\n", __func__, skb->len, d[0], d[1]);
|
|
|
|
if(data->pdata && data->pdata->hw_sdma_tx)
|
|
{
|
|
err = data->pdata->hw_sdma_tx(portno, skb->data, skb->len);
|
|
}
|
|
if(err < 0)
|
|
{
|
|
SKWBT_ERROR("btseekwave_tx_packet tx failed err:%d, pkt_len:%d", err, pkt_len);
|
|
return err;
|
|
}
|
|
kfree_skb(skb);
|
|
|
|
data->hdev->stat.byte_tx += pkt_len;
|
|
|
|
//SKWBT_INFO("%s, pkt:%d, users:%d \n", __func__, bt_cb((skb))->pkt_type, skb->users.refs.counter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void btseekwave_work(struct work_struct *work)
|
|
{
|
|
struct btseekwave_data *data = container_of(work, struct btseekwave_data, work);
|
|
struct sk_buff *skb;
|
|
int err = 0;
|
|
|
|
//SKWBT_INFO("%s %s", __func__, data->hdev->name);
|
|
|
|
if(atomic_read(&cmd_reject))
|
|
{
|
|
return ;
|
|
}
|
|
|
|
while ((skb = skb_dequeue(&data->cmd_txq)))
|
|
{
|
|
err = btseekwave_tx_packet(data->pdata->cmd_port, data, skb);
|
|
if (err < 0)
|
|
{
|
|
data->hdev->stat.err_tx++;
|
|
skb_queue_head(&data->cmd_txq, skb);
|
|
SKWBT_ERROR("btseekwave_tx_packet command failed len: %d\n", err);
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (err >= 0 && (skb = skb_dequeue(&data->data_txq)))
|
|
{
|
|
err = btseekwave_tx_packet(data->pdata->data_port, data, skb);
|
|
if (err < 0)
|
|
{
|
|
data->hdev->stat.err_tx++;
|
|
skb_queue_head(&data->data_txq, skb);
|
|
SKWBT_ERROR("btseekwave_tx_packet data failed len: %d\n", err);
|
|
break;
|
|
}
|
|
}
|
|
while (err >= 0 && (skb = skb_dequeue(&data->audio_txq)))
|
|
{
|
|
err = btseekwave_tx_packet(data->pdata->audio_port, data, skb);
|
|
if (err < 0)
|
|
{
|
|
data->hdev->stat.err_tx++;
|
|
skb_queue_head(&data->audio_txq, skb);
|
|
SKWBT_ERROR("btseekwave_tx_packet audio failed len: %d\n", err);
|
|
break;
|
|
}
|
|
}
|
|
// SKWBT_INFO("btseekwave_work done\n");
|
|
}
|
|
|
|
|
|
static int btseekwave_rx_packet(struct btseekwave_data *data, u8 pkt_type, void *buf, int c_len)
|
|
{
|
|
struct sk_buff *skb;
|
|
//SKWBT_INFO("rx hci pkt len = %d, pkt_type:%d", c_len, pkt_type);
|
|
|
|
skb = bt_skb_alloc(c_len, GFP_ATOMIC);
|
|
if (!skb)
|
|
{
|
|
SKWBT_ERROR("skwbt alloc skb failed, len: %d\n", c_len);
|
|
return 0;
|
|
}
|
|
bt_cb((skb))->expect = 0;
|
|
skb->dev = (void *) data->hdev;
|
|
bt_cb(skb)->pkt_type = pkt_type;
|
|
memcpy(skb_put(skb, c_len), buf, c_len);
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
hci_recv_frame(data->hdev, skb);
|
|
#else
|
|
hci_recv_frame(skb);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int btseekwave_rx_complete(int portno, struct scatterlist *priv, int size, void *buf)
|
|
{
|
|
int ret = 0;
|
|
struct btseekwave_data *data = (struct btseekwave_data *)priv;
|
|
u8 pkt_type = 0;
|
|
|
|
//SKWBT_INFO("btseekwave_rx_complete size=%d, is_init_mode:%d, data:0x%04X", size, is_init_mode, buf ? (*(uint32_t *)buf) : 0xFFFFFFFF);
|
|
if(size == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if(size < 0)//CP assert/exception
|
|
{
|
|
SKWBT_ERROR("cp exception\n");
|
|
return 0;
|
|
}
|
|
#if SKWBT_LOG_PORT_EN
|
|
skwbt_log_port_data_write(1, (uint8_t *)buf, (uint16_t)size);
|
|
#endif
|
|
|
|
pkt_type = *((u8 *)buf);
|
|
if(HCI_EVENT_SKWLOG == pkt_type)
|
|
{
|
|
#if BT_CP_LOG_EN
|
|
skwlog_write(buf, size);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
if((HCI_EVENT_PKT == pkt_type) || (HCI_ACLDATA_PKT == pkt_type) || (HCI_SCODATA_PKT == pkt_type))
|
|
{
|
|
#if BT_HCI_LOG_EN
|
|
skw_btsnoop_capture(buf, 1);
|
|
#endif
|
|
|
|
if(is_init_mode)//command complete event
|
|
{
|
|
hci_cmd_cmpl_evt_st *hci_evt = (hci_cmd_cmpl_evt_st *)buf;
|
|
if((HCI_EVENT_PKT == pkt_type) && (HCI_COMMAND_COMPLETE_EVENT == hci_evt->evt_op) && (HCI_CMD_READ_LOCAL_VERSION_INFO == hci_evt->cmd_op))
|
|
{
|
|
struct hci_rp_read_local_version *ver;
|
|
ver = (struct hci_rp_read_local_version *)(buf + 6);
|
|
chip_version = le16_to_cpu(ver->hci_rev);
|
|
SKWBT_INFO("%s, chip version:0x%X", __func__, chip_version);
|
|
}
|
|
|
|
atomic_inc(&evt_recv);
|
|
wake_up(&nv_wait_queue);
|
|
SKWBT_INFO("init cmd response: 0x%x \n", *((u32 *)(buf + 3)));
|
|
return 0;
|
|
}
|
|
|
|
ret = btseekwave_rx_packet(data, pkt_type, buf + 1, size - 1);
|
|
}
|
|
else
|
|
{
|
|
SKWBT_ERROR("err hci packet: %x, len:%d\n", pkt_type, size);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct sk_buff *btseekwave_prepare_cmd(struct hci_dev *hdev, u16 opcode, u32 plen,
|
|
const void *param)
|
|
{
|
|
int len = HCI_COMMAND_HDR_SIZE + plen;
|
|
struct hci_command_hdr *hdr;
|
|
struct sk_buff *skb;
|
|
|
|
skb = bt_skb_alloc(len, GFP_ATOMIC);
|
|
if (!skb)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
hdr = (struct hci_command_hdr *) skb_put(skb, HCI_COMMAND_HDR_SIZE);
|
|
hdr->opcode = cpu_to_le16(opcode);
|
|
hdr->plen = plen;
|
|
|
|
if (plen)
|
|
{
|
|
uint8_t *base_ptr = (uint8_t *)skb_put(skb, plen);
|
|
if(base_ptr)//for Coverity scan
|
|
{
|
|
memcpy(base_ptr, param, plen);
|
|
}
|
|
}
|
|
|
|
bt_cb(skb)->pkt_type = HCI_COMMAND_PKT;
|
|
|
|
return skb;
|
|
}
|
|
|
|
|
|
void btseekwave_write_bd_addr(struct hci_dev *hdev)
|
|
{
|
|
u8 cmd_pld[32] = {0x00};
|
|
//struct sk_buff *skb;
|
|
int ret;
|
|
if(bd_addr)
|
|
{
|
|
uint8_t i = 0, j, size = skw_strlen(bd_addr);
|
|
SKWBT_INFO("%s bd addr:%s", __func__, bd_addr);
|
|
//BC:9A:98:86:74:62
|
|
if(size != 17)
|
|
{
|
|
return ;
|
|
}
|
|
for (i = 16, j = 0; j < 6; i -= 3, j++)
|
|
{
|
|
cmd_pld[j] = (skw_char2hex(bd_addr[i - 1]) << 4) | skw_char2hex(bd_addr[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!skw_get_bd_addr(cmd_pld))//bd addr is invalid
|
|
{
|
|
return ;
|
|
}
|
|
}
|
|
|
|
ret = btseekwave_send_hci_command(hdev, HCI_CMD_WRITE_BD_ADDR, BD_ADDR_LEN, cmd_pld);
|
|
if(ret < 0)
|
|
{
|
|
SKWBT_ERROR("%s write bd_addr timeout", __func__);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
0: success
|
|
other:fail
|
|
*/
|
|
int btseekwave_send_hci_command(struct hci_dev *hdev, u16 opcode, int len, char *cmd_pld)
|
|
{
|
|
struct sk_buff *skb;
|
|
int ret = 0, i;
|
|
|
|
skb = btseekwave_prepare_cmd(hdev, opcode, len, cmd_pld);
|
|
if(!skb)
|
|
{
|
|
SKWBT_ERROR("%s no memory for command", __func__);
|
|
return -1;
|
|
}
|
|
//waiting controller response
|
|
atomic_set(&evt_recv, 0);
|
|
|
|
ret = btseekwave_send_frame(hdev, skb);
|
|
if(ret != 0)
|
|
{
|
|
SKWBT_ERROR("%s cmd send fail, ret:%d", __func__, ret);
|
|
return -1;
|
|
}
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
ret = wait_event_timeout(nv_wait_queue, (atomic_read(&evt_recv)), msecs_to_jiffies(1000));
|
|
if((ret > 0) || (atomic_read(&evt_recv)))
|
|
{
|
|
return 0;
|
|
}
|
|
SKWBT_INFO("%s cp response timeout, ret:%d", __func__, ret);
|
|
if(ret == 0)//timeout
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void btseekwave_write_ble_wakeup_adv_info(struct hci_dev *hdev)
|
|
{
|
|
uint8_t adv_data_len = wakeup_adv_info.data_len;
|
|
if(adv_data_len > 0)
|
|
{
|
|
uint8_t p_buf[256] = {0x00};
|
|
uint8_t *ptr = p_buf;
|
|
uint8_t i, adv_len;
|
|
uint8_t pld_len = adv_data_len + 4;//add the length of gpio & level & grp nums & total len
|
|
Wakeup_ADV_Grp_St *adv_grp;
|
|
|
|
UINT8_TO_STREAM(ptr, wakeup_adv_info.gpio_no);
|
|
UINT8_TO_STREAM(ptr, wakeup_adv_info.level);
|
|
UINT8_TO_STREAM(ptr, wakeup_adv_info.grp_nums);
|
|
UINT8_TO_STREAM(ptr, adv_data_len);
|
|
for(i = 0; i < wakeup_adv_info.grp_nums; i++)
|
|
{
|
|
adv_grp = &wakeup_adv_info.adv_group[i];
|
|
UINT8_TO_STREAM(ptr, adv_grp->grp_len);
|
|
UINT8_TO_STREAM(ptr, adv_grp->addr_offset);
|
|
adv_len = (adv_grp->grp_len - 2) >> 1;
|
|
|
|
SKWBT_INFO("grp len:%d, adv_len:%d", adv_grp->grp_len, adv_len);
|
|
|
|
memcpy(ptr, adv_grp->data, adv_len);
|
|
ptr += adv_len;
|
|
memcpy(ptr, adv_grp->mask, adv_len);
|
|
ptr += adv_len;
|
|
}
|
|
btseekwave_send_hci_command(hdev, HCI_CMD_WRITE_WAKEUP_ADV_DATA, pld_len, p_buf);
|
|
}
|
|
}
|
|
|
|
void btseekwave_write_ble_wakeup_adv_enable(le_wakeup_op_enum enable_op)
|
|
{
|
|
if((wakeup_adv_info.data_len > 0) && skw_data && (skw_data->bt_is_open))
|
|
{
|
|
char buffer[2] = {0x00};
|
|
struct sk_buff *skb;
|
|
struct hci_dev *hdev = skw_data->hdev;
|
|
|
|
buffer[0] = enable_op;
|
|
|
|
skb = btseekwave_prepare_cmd(hdev, HCI_CMD_WRITE_WAKEUP_ADV_ENABLE_PLT, 1, buffer);
|
|
if(!skb)
|
|
{
|
|
SKWBT_ERROR("%s no memory for nv command", __func__);
|
|
return ;
|
|
}
|
|
SKWBT_INFO("%s", __func__);
|
|
btseekwave_send_frame(hdev, skb);
|
|
|
|
msleep(5);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(btseekwave_write_ble_wakeup_adv_enable);
|
|
|
|
static void btseekwave_port_close(struct btseekwave_data *data)
|
|
{
|
|
if(data && data->pdata)
|
|
{
|
|
data->bt_is_open = 0;
|
|
|
|
if(data->pdata->modem_unregister_notify && data->plt_notifier_set)
|
|
{
|
|
data->plt_notifier_set = 0;
|
|
data->pdata->modem_unregister_notify(&data->plt_notifier);
|
|
}
|
|
#if INCLUDE_NEW_VERSION
|
|
if(data->pdata->service_stop)
|
|
{
|
|
data->pdata->service_stop();
|
|
}
|
|
else
|
|
{
|
|
SKWBT_ERROR("func %s service_stop not exist", __func__);
|
|
}
|
|
#else
|
|
skw_stop_bt_service();
|
|
#endif
|
|
if(data->pdata->close_port)
|
|
{
|
|
data->pdata->close_port(data->pdata->cmd_port);
|
|
if(data->pdata->data_port != 0)
|
|
{
|
|
data->pdata->close_port(data->pdata->data_port);
|
|
}
|
|
if(data->pdata->audio_port != 0)
|
|
{
|
|
data->pdata->close_port(data->pdata->audio_port);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int btseekwave_download_nv(struct hci_dev *hdev)
|
|
{
|
|
int page_offset = 0, ret = 0, len = 0;
|
|
u8 *cmd_pld = NULL;
|
|
const struct firmware *fw;
|
|
int err = 0, count = 0;
|
|
uint8_t log_disable = 1, cp_log_disable = 1;
|
|
SKWBT_INFO("%s", __func__);
|
|
|
|
is_init_mode = 1;
|
|
chip_version = SKW_CHIPID_6160;
|
|
|
|
ret = btseekwave_send_hci_command(hdev, HCI_CMD_READ_LOCAL_VERSION_INFO, 0, NULL);
|
|
if(ret < 0)
|
|
{
|
|
SKWBT_ERROR("%s, read local version err", __func__);
|
|
if(skw_data)
|
|
{
|
|
skw_data->pdata->modem_assert();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if(SKW_CHIPID_6316 == chip_version)
|
|
{
|
|
err = request_firmware(&fw, NV_FILE_NAME_6316, &hdev->dev);
|
|
}
|
|
else if(SKW_CHIPID_6160_LITE == chip_version)
|
|
{
|
|
err = request_firmware(&fw, NV_FILE_NAME_6160_LITE, &hdev->dev);
|
|
}
|
|
else
|
|
{
|
|
err = request_firmware(&fw, NV_FILE_NAME, &hdev->dev);
|
|
}
|
|
if (err < 0)
|
|
{
|
|
SKWBT_ERROR("nv file load fail, BT Controller Version:0x%04X", chip_version);
|
|
return err;
|
|
}
|
|
cmd_pld = (u8 *)kzalloc(512, GFP_KERNEL);
|
|
if(cmd_pld == NULL)
|
|
{
|
|
SKWBT_ERROR("%s malloc fail", __func__);
|
|
release_firmware(fw);
|
|
return -1;
|
|
}
|
|
#if ((BT_CP_LOG_EN == 1) || (BT_HCI_LOG_EN == 1))
|
|
skwbt_log_disable = 0;
|
|
#endif
|
|
|
|
#if BT_CP_LOG_EN
|
|
cp_log_disable = 0;
|
|
#endif
|
|
|
|
|
|
if((SKW_CHIPID_6316 == chip_version) || (SKW_CHIPID_6160_LITE == chip_version))
|
|
{
|
|
int total_len = 0;
|
|
int nv_pkt_len = 0;
|
|
uint8_t nv_tag = 0;
|
|
uint8_t *base_ptr = NULL;
|
|
count = 4;//skip header
|
|
while(count < fw->size)
|
|
{
|
|
nv_tag = fw->data[count];
|
|
nv_pkt_len = fw->data[count + 2] + 3;
|
|
if((nv_pkt_len + total_len) >= NV_FILE_RD_BLOCK_SIZE)
|
|
{
|
|
cmd_pld[0] = (char)page_offset;
|
|
cmd_pld[1] = (char)total_len;//para len
|
|
ret = btseekwave_send_hci_command(hdev, HCI_CMD_SKW_BT_NVDS, total_len + 2, cmd_pld);
|
|
if(ret < 0)
|
|
{
|
|
//return -1;
|
|
total_len = 0;
|
|
err = -1;
|
|
break;
|
|
}
|
|
page_offset ++;
|
|
total_len = 0;
|
|
continue;
|
|
}
|
|
base_ptr = cmd_pld + 2 + total_len;
|
|
if(base_ptr)
|
|
{
|
|
memcpy(base_ptr, fw->data + count, nv_pkt_len);
|
|
}
|
|
if(nv_tag == NV_TAG_DSP_LOG_SETTING)
|
|
{
|
|
log_disable = fw->data[count + 3];
|
|
if(cp_log_disable)
|
|
{
|
|
log_disable = 1;
|
|
}
|
|
if(total_len < NV_FILE_RD_BLOCK_SIZE)//for Coverity scan
|
|
{
|
|
*(cmd_pld + 2 + total_len + 3) = log_disable;
|
|
}
|
|
SKWBT_INFO("%s log_disable from NV:%d, skwbt_log_disable:%d", __func__, log_disable, cp_log_disable);
|
|
}
|
|
count += nv_pkt_len;
|
|
total_len += nv_pkt_len;
|
|
}
|
|
if(total_len > 0)
|
|
{
|
|
cmd_pld[0] = (char)page_offset;
|
|
cmd_pld[1] = (char)total_len;//para len
|
|
ret = btseekwave_send_hci_command(hdev, HCI_CMD_SKW_BT_NVDS, total_len + 2, cmd_pld);
|
|
if(ret < 0)
|
|
{
|
|
SKWBT_ERROR("%s, line:%d, cp response timeout", __func__, __LINE__);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_disable = fw->data[0x131];
|
|
SKWBT_INFO("%s log_disable from NV:%d, skwbt_log_disable:%d", __func__, log_disable, cp_log_disable);
|
|
while(count < fw->size)
|
|
{
|
|
len = NV_FILE_RD_BLOCK_SIZE;
|
|
if((fw->size - count) < NV_FILE_RD_BLOCK_SIZE)
|
|
{
|
|
len = fw->size - count;
|
|
}
|
|
cmd_pld[0] = (char)page_offset;
|
|
cmd_pld[1] = (char)len;//para len
|
|
memcpy(cmd_pld + 2, fw->data + count, len);
|
|
count += len;
|
|
|
|
if(1 == page_offset)
|
|
{
|
|
if(cp_log_disable)
|
|
{
|
|
log_disable = 1;
|
|
}
|
|
*(cmd_pld + 2 + 53) = log_disable;
|
|
}
|
|
|
|
ret = btseekwave_send_hci_command(hdev, HCI_CMD_SKW_BT_NVDS, len + 2, cmd_pld);
|
|
if(ret < 0)
|
|
{
|
|
SKWBT_ERROR("%s, line:%d, cp response timeout", __func__, __LINE__);
|
|
break;
|
|
}
|
|
page_offset ++;
|
|
}
|
|
}
|
|
|
|
if(err == 0)
|
|
{
|
|
btseekwave_write_bd_addr(hdev);
|
|
btseekwave_write_ble_wakeup_adv_info(hdev);
|
|
}
|
|
|
|
kfree(cmd_pld);
|
|
release_firmware(fw);
|
|
is_init_mode = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int btseekwave_open(struct hci_dev *hdev)
|
|
{
|
|
struct btseekwave_data *data = hci_get_drvdata(hdev);
|
|
int err = -1;
|
|
|
|
SKWBT_INFO("%s enter...\n", __func__);
|
|
|
|
if(atomic_read(&cmd_reject))
|
|
{
|
|
int ret = wait_event_timeout(recovery_wait_queue,
|
|
(!atomic_read(&cmd_reject)),
|
|
msecs_to_jiffies(2000));
|
|
if(!ret)
|
|
{
|
|
SKWBT_ERROR("%s timeout", __func__);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if(data && data->pdata && data->pdata->open_port)
|
|
{
|
|
SKWBT_INFO("%s, cmd_port:%d, mode data_port:%d, audio_port:%d\n", __func__, data->pdata->cmd_port, data->pdata->data_port, data->pdata->audio_port);
|
|
|
|
err = data->pdata->open_port(data->pdata->cmd_port, btseekwave_rx_complete, data);
|
|
if(err < 0)
|
|
{
|
|
SKWBT_ERROR("command port open fail, ret:%d", err);
|
|
return err;
|
|
}
|
|
|
|
if(data->pdata->data_port != 0)
|
|
{
|
|
err = data->pdata->open_port(data->pdata->data_port, btseekwave_rx_complete, data);
|
|
if(err < 0)
|
|
{
|
|
SKWBT_ERROR("data port open fail, ret:%d", err);
|
|
return err;
|
|
}
|
|
}
|
|
if(data->pdata->audio_port != 0)
|
|
{
|
|
err = data->pdata->open_port(data->pdata->audio_port, btseekwave_rx_complete, data);
|
|
if(err < 0)
|
|
{
|
|
SKWBT_ERROR("audio port open fail, ret:%d", err);
|
|
return err;
|
|
}
|
|
}
|
|
#if INCLUDE_NEW_VERSION
|
|
if(data->pdata->service_start)
|
|
{
|
|
err = data->pdata->service_start();
|
|
if(err != 0)
|
|
{
|
|
SKWBT_ERROR("func %s service_start err:%d", __func__, err);
|
|
return err;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SKWBT_ERROR("func %s service_start not exist", __func__);
|
|
return -1;
|
|
}
|
|
#else
|
|
err = skw_start_bt_service();
|
|
if(err != 0)
|
|
{
|
|
SKWBT_ERROR("%s service_start err:%d", __func__, err);
|
|
return err;
|
|
}
|
|
#endif
|
|
err = btseekwave_download_nv(hdev);
|
|
if(err == 0)
|
|
{
|
|
data->bt_is_open = 1;
|
|
if(data->plt_notifier_set == 0)
|
|
{
|
|
data->plt_notifier.notifier_call = btseekwave_plt_event_notifier;
|
|
data->pdata->modem_register_notify(&data->plt_notifier);
|
|
data->plt_notifier_set = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
btseekwave_port_close(data);
|
|
}
|
|
|
|
}
|
|
atomic_set(&atomic_close_sync, 0);
|
|
return err;
|
|
}
|
|
|
|
void btseekwave_write_bt_state(struct hci_dev *hdev)
|
|
{
|
|
//char buffer[10] = {0x01, 0x80, 0xFE, 0x01, 0x00};
|
|
u8 cmd_pld[5] = {0x00};
|
|
struct sk_buff *skb = btseekwave_prepare_cmd(hdev, HCI_CMD_WRITE_BT_STATE, 1, cmd_pld);
|
|
if(skb)
|
|
{
|
|
btseekwave_send_frame(hdev, skb);
|
|
msleep(15);
|
|
}
|
|
}
|
|
|
|
|
|
static int btseekwave_close(struct hci_dev *hdev)
|
|
{
|
|
struct btseekwave_data *data = hci_get_drvdata(hdev);
|
|
int state = 0;
|
|
|
|
SKWBT_INFO("%s enter...\n", __func__);
|
|
|
|
if(data && (data->pdata->data_port == 0))
|
|
{
|
|
#if INCLUDE_NEW_VERSION
|
|
|
|
#else
|
|
btseekwave_write_bt_state(hdev);
|
|
#endif
|
|
}
|
|
|
|
if(atomic_read(&cmd_reject))
|
|
{
|
|
int ret = wait_event_timeout(recovery_wait_queue,
|
|
(!atomic_read(&cmd_reject)),
|
|
msecs_to_jiffies(2000));
|
|
if(!ret)
|
|
{
|
|
SKWBT_ERROR("%s timeout, ret:%d", __func__, ret);
|
|
}
|
|
}
|
|
btseekwave_port_close(data);
|
|
|
|
state = atomic_read(&atomic_close_sync);
|
|
SKWBT_INFO("func %s, atomic_read:%d", __func__, state);
|
|
|
|
if(state == BT_STATE_DEFAULT)
|
|
{
|
|
atomic_set(&atomic_close_sync, BT_STATE_CLOSE);
|
|
}
|
|
else
|
|
{
|
|
atomic_set(&atomic_close_sync, BT_STATE_CLOSE);
|
|
wake_up(&close_wait_queue);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int btseekwave_flush(struct hci_dev *hdev)
|
|
{
|
|
struct btseekwave_data *data = hci_get_drvdata(hdev);
|
|
|
|
SKWBT_INFO("%s", hdev->name);
|
|
|
|
if (work_pending(&data->work))
|
|
{
|
|
cancel_work_sync(&data->work);
|
|
}
|
|
|
|
skb_queue_purge(&data->cmd_txq);
|
|
skb_queue_purge(&data->data_txq);
|
|
skb_queue_purge(&data->audio_txq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
//
|
|
#else
|
|
/*for low version*/
|
|
static int btseekwave_send_frame_lv(struct sk_buff *skb)
|
|
{
|
|
struct hci_dev *hdev = (struct hci_dev *) skb->dev;
|
|
return btseekwave_send_frame(hdev, skb);
|
|
}
|
|
#endif
|
|
|
|
static int btseekwave_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
struct btseekwave_data *data = hci_get_drvdata(hdev);
|
|
u8 pkt_type = bt_cb(skb)->pkt_type;
|
|
u8 *d = skb_push(skb, 1);
|
|
*d = pkt_type;
|
|
|
|
if(data->pdata == NULL)
|
|
{
|
|
SKWBT_ERROR("%s pointer is null", __func__);
|
|
return -EILSEQ;
|
|
}
|
|
if((pkt_type == HCI_COMMAND_PKT) || ((pkt_type == HCI_ACLDATA_PKT) && (data->pdata->data_port == 0))
|
|
|| ((pkt_type == HCI_SCODATA_PKT) && (data->pdata->audio_port == 0)))
|
|
{
|
|
hdev->stat.cmd_tx++;
|
|
skb_queue_tail(&data->cmd_txq, skb);
|
|
}
|
|
else if(pkt_type == HCI_ACLDATA_PKT)
|
|
{
|
|
hdev->stat.acl_tx++;
|
|
skb_queue_tail(&data->data_txq, skb);
|
|
}
|
|
else if(pkt_type == HCI_SCODATA_PKT)
|
|
{
|
|
skb_queue_tail(&data->audio_txq, skb);
|
|
hdev->stat.sco_tx++;
|
|
}
|
|
else
|
|
{
|
|
return -EILSEQ;
|
|
}
|
|
|
|
#if BT_HCI_LOG_EN
|
|
skw_btsnoop_capture(skb->data, 0);
|
|
#endif
|
|
|
|
#if SKWBT_LOG_PORT_EN
|
|
skwbt_log_port_data_write(0, skb->data, (uint16_t)skb->len);
|
|
#endif
|
|
|
|
schedule_work(&data->work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int btseekwave_setup(struct hci_dev *hdev)
|
|
{
|
|
SKWBT_INFO("%s", __func__);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
must be in DEVICE_ASSERT_EVENT to DEVICE_DUMPDONE_EVENT closing USB
|
|
*/
|
|
int btseekwave_plt_event_notifier(struct notifier_block *nb, unsigned long action, void *param)
|
|
{
|
|
SKWBT_INFO("%s, action:%d", __func__, (int)action);
|
|
if(skw_data == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
#if 1
|
|
switch(action)
|
|
{
|
|
case DEVICE_ASSERT_EVENT:
|
|
{
|
|
//make surce host data cann't send to plt driver before close usb
|
|
atomic_set(&cmd_reject, 1);
|
|
#if INCLUDE_NEW_VERSION
|
|
if((skw_data) && (skw_data->pdata) && (skw_data->pdata->service_stop))
|
|
{
|
|
skw_data->pdata->service_stop();
|
|
}
|
|
else
|
|
{
|
|
SKWBT_ERROR("func %s service_stop not exist", __func__);
|
|
}
|
|
#else
|
|
skw_stop_bt_service();
|
|
#endif
|
|
}
|
|
break;
|
|
case DEVICE_BSPREADY_EVENT://
|
|
{
|
|
if(atomic_read(&cmd_reject))
|
|
{
|
|
struct btseekwave_data *data = skw_data;//container_of(nb, struct btseekwave_data, plt_notifier);
|
|
atomic_set(&cmd_reject, 0);
|
|
wake_up(&recovery_wait_queue);
|
|
|
|
if(data)
|
|
{
|
|
btseekwave_flush(data->hdev);
|
|
btseekwave_hci_hardware_error(data->hdev);//report to host
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DEVICE_DUMPDONE_EVENT:
|
|
{
|
|
|
|
}
|
|
break;
|
|
case DEVICE_BLOCKED_EVENT:
|
|
{
|
|
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
|
|
}
|
|
break;
|
|
|
|
}
|
|
#endif
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int btseekwave_probe(struct platform_device *pdev)
|
|
{
|
|
struct btseekwave_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
struct sv6160_platform_data *pdata = dev->platform_data;
|
|
struct hci_dev *hdev;
|
|
int err;
|
|
if(pdata == NULL)
|
|
{
|
|
SKWBT_ERROR("%s pdata is null", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
SKWBT_INFO("%s pdev name %s\n", __func__, pdata->port_name);
|
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
{
|
|
SKWBT_ERROR("%s alloc fail", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
skw_data = data;
|
|
data->plt_notifier_set = 0;
|
|
data->bt_is_open = 0;
|
|
|
|
data->pdata = pdata;
|
|
|
|
INIT_WORK(&data->work, btseekwave_work);
|
|
|
|
skb_queue_head_init(&data->cmd_txq);
|
|
skb_queue_head_init(&data->data_txq);
|
|
skb_queue_head_init(&data->audio_txq);
|
|
|
|
hdev = hci_alloc_dev();
|
|
if (!hdev)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hdev->bus = HCI_SDIO;
|
|
hci_set_drvdata(hdev, data);
|
|
|
|
data->hdev = hdev;
|
|
|
|
SET_HCIDEV_DEV(hdev, dev);
|
|
|
|
hdev->open = btseekwave_open;
|
|
hdev->close = btseekwave_close;
|
|
hdev->flush = btseekwave_flush;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
|
|
hdev->send = btseekwave_send_frame;
|
|
#else
|
|
hdev->send = btseekwave_send_frame_lv;
|
|
#endif
|
|
hdev->setup = btseekwave_setup;
|
|
|
|
atomic_set(&hdev->promisc, 0);
|
|
|
|
//set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
|
|
//set_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks);
|
|
|
|
err = hci_register_dev(hdev);
|
|
if (err < 0)
|
|
{
|
|
hci_free_dev(hdev);
|
|
return err;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, data);
|
|
|
|
skw_bd_addr_gen_init();
|
|
atomic_set(&cmd_reject, 0);
|
|
atomic_set(&atomic_close_sync, BT_STATE_DEFAULT);
|
|
|
|
#if SKWBT_LOG_PORT_EN
|
|
skwbt_log_port_init();
|
|
skwbt_log_port_set_pdata(pdata);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int btseekwave_remove(struct platform_device *pdev)
|
|
{
|
|
int state = atomic_read(&atomic_close_sync);
|
|
|
|
SKWBT_INFO("func %s, atomic_read:%d", __func__, state);
|
|
|
|
atomic_set(&cmd_reject, 0);
|
|
if(BT_STATE_DEFAULT == state)
|
|
{
|
|
int ret;
|
|
atomic_set(&atomic_close_sync, BT_STATE_REMOVE);
|
|
ret = wait_event_timeout(close_wait_queue,
|
|
(BT_STATE_CLOSE == atomic_read(&atomic_close_sync)),
|
|
msecs_to_jiffies(500));
|
|
if(!ret)
|
|
{
|
|
SKWBT_ERROR("%s timeout, ret:%d", __func__, ret);
|
|
}
|
|
}
|
|
|
|
atomic_set(&atomic_close_sync, BT_STATE_DEFAULT);
|
|
skw_data = NULL;
|
|
if(pdev)
|
|
{
|
|
struct btseekwave_data *data = platform_get_drvdata(pdev);
|
|
struct hci_dev *hdev;
|
|
|
|
if (!data)
|
|
{
|
|
return 0;
|
|
}
|
|
hdev = data->hdev;
|
|
data->bt_is_open = 0;
|
|
|
|
if(data->pdata && data->pdata->modem_unregister_notify && data->plt_notifier_set)
|
|
{
|
|
SKWBT_INFO("func %s modem_unregister_notify", __func__);
|
|
data->pdata->modem_unregister_notify(&data->plt_notifier);
|
|
data->plt_notifier_set = 0;
|
|
}
|
|
|
|
btseekwave_flush(hdev);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
hci_unregister_dev(hdev);
|
|
|
|
hci_free_dev(hdev);
|
|
|
|
#if SKWBT_LOG_PORT_EN
|
|
skwbt_log_port_exit();
|
|
#endif
|
|
}
|
|
SKWBT_INFO("func %s end", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver btseekwave_driver =
|
|
{
|
|
.driver = {
|
|
.name = (char *)"btseekwave",
|
|
.bus = &platform_bus_type,
|
|
.pm = NULL,
|
|
},
|
|
.probe = btseekwave_probe,
|
|
.remove = btseekwave_remove,
|
|
};
|
|
|
|
int btseekwave_init(void)
|
|
{
|
|
SKWBT_INFO("Seekwave Bluetooth driver ver %s\n", VERSION);
|
|
init_waitqueue_head(&nv_wait_queue);
|
|
init_waitqueue_head(&recovery_wait_queue);
|
|
init_waitqueue_head(&close_wait_queue);
|
|
atomic_set(&evt_recv, 0);
|
|
|
|
wakeup_adv_info.data_len = 0;
|
|
|
|
#ifdef BLE_WAKEUP_ADV_INFO
|
|
skw_parse_wakeup_adv_conf(BLE_WAKEUP_ADV_INFO, &wakeup_adv_info);
|
|
#endif
|
|
|
|
#if BT_HCI_LOG_EN
|
|
skw_btsnoop_init();
|
|
#endif
|
|
#if BT_CP_LOG_EN
|
|
skwlog_init();
|
|
#endif
|
|
return platform_driver_register(&btseekwave_driver);
|
|
}
|
|
|
|
void btseekwave_exit(void)
|
|
{
|
|
#if BT_HCI_LOG_EN
|
|
skw_btsnoop_close();
|
|
#endif
|
|
#if BT_CP_LOG_EN
|
|
skwlog_close();
|
|
#endif
|
|
|
|
platform_driver_unregister(&btseekwave_driver);
|
|
}
|
|
|
|
module_init(btseekwave_init);
|
|
module_exit(btseekwave_exit);
|
|
|
|
MODULE_DESCRIPTION("Seekwave Bluetooth driver ver " VERSION);
|
|
MODULE_VERSION(VERSION);
|
|
MODULE_LICENSE("GPL");
|
|
|