| /** @file bt_main.c |
| * |
| * @brief This file contains the major functions in BlueTooth |
| * driver. It includes init, exit, open, close and main |
| * thread etc.. |
| * |
| * Copyright (C) 2007-2019, Marvell International Ltd. |
| * |
| * This software file (the "File") is distributed by Marvell International |
| * Ltd. under the terms of the GNU General Public License Version 2, June 1991 |
| * (the "License"). You may use, redistribute and/or modify this File in |
| * accordance with the terms and conditions of the License, a copy of which |
| * is available along with the File in the gpl.txt file or by writing to |
| * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| * 02111-1307 or on the worldwide web at http://www.gnu.org/licenses/gpl.txt. |
| * |
| * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE |
| * ARE EXPRESSLY DISCLAIMED. The License provides additional details about |
| * this warranty disclaimer. |
| * |
| */ |
| /** |
| * @mainpage M-BT Linux Driver |
| * |
| * @section overview_sec Overview |
| * |
| * The M-BT is a Linux reference driver for Marvell Bluetooth chipset. |
| * |
| * @section copyright_sec Copyright |
| * |
| * Copyright (C) 2007-2019, Marvell International Ltd. |
| * |
| */ |
| #include <linux/module.h> |
| |
| #ifdef CONFIG_OF |
| #include <linux/of.h> |
| #endif |
| |
| #include <linux/mmc/sdio_func.h> |
| |
| #include "bt_drv.h" |
| #include "mbt_char.h" |
| #include "bt_sdio.h" |
| |
| /** Version */ |
| #define VERSION "C4X14113" |
| |
| /** Driver version */ |
| static char mbt_driver_version[] = "SD8XXX-%s-" VERSION "-(" "FP" FPNUM ")" |
| #ifdef DEBUG_LEVEL2 |
| "-dbg" |
| #endif |
| " "; |
| |
| /** SD8787 Card */ |
| #define CARD_SD8787 "SD8787" |
| /** SD8777 Card */ |
| #define CARD_SD8777 "SD8777" |
| /** SD8887 Card */ |
| #define CARD_SD8887 "SD8887" |
| /** SD8897 Card */ |
| #define CARD_SD8897 "SD8897" |
| /** SD8797 Card */ |
| #define CARD_SD8797 "SD8797" |
| /** SD8977 Card */ |
| #define CARD_SD8977 "SD8977" |
| /** SD8978 Card */ |
| #define CARD_SD8978 "SD8978" |
| /** SD8997 Card */ |
| #define CARD_SD8997 "SD8997" |
| /** SD8987 Card */ |
| #define CARD_SD8987 "SD8987" |
| |
| /** Declare and initialize fw_version */ |
| static char fw_version[32] = "0.0.0.p0"; |
| |
| #define AID_SYSTEM 1000 /* system server */ |
| |
| #define AID_BLUETOOTH 1002 /* bluetooth subsystem */ |
| |
| #define AID_NET_BT_STACK 3008 /* bluetooth stack */ |
| |
| /** Define module name */ |
| |
| #define MODULE_NAME "bt_fm_nfc" |
| |
| /** Declaration of chardev class */ |
| static struct class *chardev_class; |
| |
| /** Interface specific variables */ |
| |
| /** |
| * The global variable of a pointer to bt_private |
| * structure variable |
| **/ |
| bt_private *m_priv[MAX_BT_ADAPTER]; |
| |
| /** Default Driver mode */ |
| static int drv_mode = (DRV_MODE_BT); |
| |
| /** fw reload flag */ |
| int bt_fw_reload; |
| /** fw serial download flag */ |
| int bt_fw_serial = 1; |
| |
| /** Firmware flag */ |
| static int fw = 1; |
| /** default powermode */ |
| static int psmode = 1; |
| /** default BLE deep sleep */ |
| static int deep_sleep = 1; |
| /** Default CRC check control */ |
| static int fw_crc_check = 1; |
| /** Init config file (MAC address, register etc.) */ |
| static char *init_cfg; |
| /** Calibration config file (MAC address, init powe etc.) */ |
| static char *cal_cfg; |
| /** Calibration config file EXT */ |
| static char *cal_cfg_ext; |
| /** Init MAC address */ |
| static char *bt_mac; |
| static int btindrst = -1; |
| |
| /** Setting mbt_drvdbg value based on DEBUG level */ |
| #ifdef DEBUG_LEVEL1 |
| #ifdef DEBUG_LEVEL2 |
| #define DEFAULT_DEBUG_MASK (0xffffffff & ~DBG_EVENT) |
| #else |
| #define DEFAULT_DEBUG_MASK (DBG_MSG | DBG_FATAL | DBG_ERROR) |
| #endif /* DEBUG_LEVEL2 */ |
| u32 mbt_drvdbg = DEFAULT_DEBUG_MASK; |
| #endif |
| |
| #ifdef CONFIG_OF |
| static int dts_enable = 1; |
| #endif |
| |
| #ifdef SDIO_SUSPEND_RESUME |
| /** PM keep power */ |
| int mbt_pm_keep_power = 1; |
| #endif |
| |
| static int btpmic = 0; |
| |
| /** Offset of sequence number in event */ |
| #define OFFSET_SEQNUM 4 |
| |
| /** |
| * @brief handle received packet |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to rx skb |
| * |
| * @return N/A |
| */ |
| void |
| bt_recv_frame(bt_private *priv, struct sk_buff *skb) |
| { |
| struct hci_dev *hdev = NULL; |
| if (priv->bt_dev.m_dev[BT_SEQ].spec_type == BLUEZ_SPEC) |
| hdev = (struct hci_dev *)priv->bt_dev.m_dev[BT_SEQ].dev_pointer; |
| if (hdev) { |
| skb->dev = (void *)hdev; |
| hdev->stat.byte_rx += skb->len; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| hci_recv_frame(skb); |
| #else |
| hci_recv_frame(hdev, skb); |
| #endif |
| } |
| return; |
| } |
| |
| /** |
| * @brief Alloc bt device |
| * |
| * @return pointer to structure mbt_dev or NULL |
| */ |
| struct mbt_dev * |
| alloc_mbt_dev(void) |
| { |
| struct mbt_dev *mbt_dev; |
| ENTER(); |
| |
| mbt_dev = kzalloc(sizeof(struct mbt_dev), GFP_KERNEL); |
| if (!mbt_dev) { |
| LEAVE(); |
| return NULL; |
| } |
| |
| LEAVE(); |
| return mbt_dev; |
| } |
| |
| /** |
| * @brief Frees m_dev |
| * |
| * @return N/A |
| */ |
| void |
| free_m_dev(struct m_dev *m_dev) |
| { |
| ENTER(); |
| kfree(m_dev->dev_pointer); |
| m_dev->dev_pointer = NULL; |
| LEAVE(); |
| } |
| |
| /** |
| * @brief clean up m_devs |
| * |
| * @return N/A |
| */ |
| void |
| clean_up_m_devs(bt_private *priv) |
| { |
| struct m_dev *m_dev = NULL; |
| struct hci_dev *hdev = NULL; |
| |
| ENTER(); |
| if (priv->bt_dev.m_dev[BT_SEQ].dev_pointer) { |
| m_dev = &(priv->bt_dev.m_dev[BT_SEQ]); |
| PRINTM(MSG, "BT: Delete %s\n", m_dev->name); |
| if (m_dev->spec_type == BLUEZ_SPEC) { |
| hdev = (struct hci_dev *)m_dev->dev_pointer; |
| /** check if dev->name has been assigned */ |
| if (strstr(hdev->name, "hci")) |
| hci_unregister_dev(hdev); |
| hci_free_dev(hdev); |
| } |
| priv->bt_dev.m_dev[BT_SEQ].dev_pointer = NULL; |
| } |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief This function verify the received event pkt |
| * |
| * Event format: |
| * +--------+--------+--------+--------+--------+ |
| * | Event | Length | ncmd | Opcode | |
| * +--------+--------+--------+--------+--------+ |
| * | 1-byte | 1-byte | 1-byte | 2-byte | |
| * +--------+--------+--------+--------+--------+ |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to rx skb |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| check_evtpkt(bt_private *priv, struct sk_buff *skb) |
| { |
| struct hci_event_hdr *hdr = (struct hci_event_hdr *)skb->data; |
| struct hci_ev_cmd_complete *ec; |
| u16 opcode, ocf; |
| int ret = BT_STATUS_SUCCESS; |
| ENTER(); |
| if (!priv->bt_dev.sendcmdflag) { |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| if (hdr->evt == HCI_EV_CMD_COMPLETE) { |
| ec = (struct hci_ev_cmd_complete *) |
| (skb->data + HCI_EVENT_HDR_SIZE); |
| opcode = __le16_to_cpu(ec->opcode); |
| ocf = hci_opcode_ocf(opcode); |
| PRINTM(CMD, |
| "BT: CMD_COMPLTE opcode=0x%x, ocf=0x%x, send_cmd_opcode=0x%x\n", |
| opcode, ocf, priv->bt_dev.send_cmd_opcode); |
| if (opcode != priv->bt_dev.send_cmd_opcode) { |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| switch (ocf) { |
| case BT_CMD_MODULE_CFG_REQ: |
| case BT_CMD_BLE_DEEP_SLEEP: |
| case BT_CMD_CONFIG_MAC_ADDR: |
| case BT_CMD_CSU_WRITE_REG: |
| case BT_CMD_LOAD_CONFIG_DATA: |
| case BT_CMD_LOAD_CONFIG_DATA_EXT: |
| case BT_CMD_AUTO_SLEEP_MODE: |
| case BT_CMD_HOST_SLEEP_CONFIG: |
| case BT_CMD_SDIO_PULL_CFG_REQ: |
| case BT_CMD_SET_EVT_FILTER: |
| // case BT_CMD_ENABLE_DEVICE_TESTMODE: |
| case BT_CMD_PMIC_CONFIGURE: |
| case BT_CMD_INDEPENDENT_RESET: |
| priv->bt_dev.sendcmdflag = FALSE; |
| priv->adapter->cmd_complete = TRUE; |
| wake_up_interruptible(&priv->adapter->cmd_wait_q); |
| break; |
| case BT_CMD_GET_FW_VERSION: |
| { |
| u8 *pos = (skb->data + HCI_EVENT_HDR_SIZE + |
| sizeof(struct hci_ev_cmd_complete) + |
| 1); |
| snprintf(fw_version, sizeof(fw_version), |
| "%u.%u.%u.p%u", pos[2], pos[1], pos[0], |
| pos[3]); |
| priv->bt_dev.sendcmdflag = FALSE; |
| priv->adapter->cmd_complete = TRUE; |
| wake_up_interruptible(&priv->adapter-> |
| cmd_wait_q); |
| break; |
| } |
| case BT_CMD_RESET: |
| case BT_CMD_ENABLE_WRITE_SCAN: |
| { |
| priv->bt_dev.sendcmdflag = FALSE; |
| priv->adapter->cmd_complete = TRUE; |
| if (priv->adapter->wait_event_timeout == TRUE) { |
| wake_up(&priv->adapter->cmd_wait_q); |
| priv->adapter->wait_event_timeout = |
| FALSE; |
| } else |
| wake_up_interruptible(&priv->adapter-> |
| cmd_wait_q); |
| } |
| break; |
| case BT_CMD_HOST_SLEEP_ENABLE: |
| priv->bt_dev.sendcmdflag = FALSE; |
| break; |
| default: |
| /** Ignore command not defined but send by driver */ |
| if (opcode == priv->bt_dev.send_cmd_opcode) { |
| priv->bt_dev.sendcmdflag = FALSE; |
| priv->adapter->cmd_complete = TRUE; |
| wake_up_interruptible(&priv->adapter-> |
| cmd_wait_q); |
| } else { |
| ret = BT_STATUS_FAILURE; |
| } |
| break; |
| } |
| } else |
| ret = BT_STATUS_FAILURE; |
| exit: |
| if (ret == BT_STATUS_SUCCESS) |
| kfree_skb(skb); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function stores the FW dumps received from events |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to rx skb |
| * |
| * @return N/A |
| */ |
| void |
| bt_store_firmware_dump(bt_private *priv, u8 *buf, u32 len) |
| { |
| struct file *pfile_fwdump = NULL; |
| loff_t pos = 0; |
| u16 seqnum = 0; |
| struct timeval t; |
| u32 sec; |
| |
| ENTER(); |
| |
| seqnum = __le16_to_cpu(*(u16 *) (buf + OFFSET_SEQNUM)); |
| |
| if (priv->adapter->fwdump_fname && seqnum != 1) { |
| pfile_fwdump = |
| filp_open((const char *)priv->adapter->fwdump_fname, |
| O_CREAT | O_WRONLY | O_APPEND, 0644); |
| if (IS_ERR(pfile_fwdump)) { |
| PRINTM(MSG, "Cannot create firmware dump file.\n"); |
| LEAVE(); |
| return; |
| } |
| } else { |
| if (!priv->adapter->fwdump_fname) { |
| gfp_t flag; |
| flag = (in_atomic() || |
| irqs_disabled())? GFP_ATOMIC : GFP_KERNEL; |
| priv->adapter->fwdump_fname = kzalloc(64, flag); |
| } else |
| memset(priv->adapter->fwdump_fname, 0, 64); |
| |
| do_gettimeofday(&t); |
| sec = (u32)t.tv_sec; |
| sprintf(priv->adapter->fwdump_fname, "%s%u", |
| "/var/log/bt_fwdump_", sec); |
| pfile_fwdump = |
| filp_open(priv->adapter->fwdump_fname, |
| O_CREAT | O_WRONLY | O_APPEND, 0644); |
| if (IS_ERR(pfile_fwdump)) { |
| sprintf(priv->adapter->fwdump_fname, "%s%u", |
| "/data/bt_fwdump_", sec); |
| pfile_fwdump = |
| filp_open((const char *)priv->adapter-> |
| fwdump_fname, |
| O_CREAT | O_WRONLY | O_APPEND, 0644); |
| } |
| } |
| |
| if (IS_ERR(pfile_fwdump)) { |
| PRINTM(MSG, "Cannot create firmware dump file\n"); |
| LEAVE(); |
| return; |
| } |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) |
| vfs_write(pfile_fwdump, buf, len, &pos); |
| #else |
| kernel_write(pfile_fwdump, buf, len, &pos); |
| #endif |
| filp_close(pfile_fwdump, NULL); |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief This function process the received event |
| * |
| * Event format: |
| * +--------+--------+--------+--------+-----+ |
| * | EC | Length | Data | |
| * +--------+--------+--------+--------+-----+ |
| * | 1-byte | 1-byte | n-byte | |
| * +--------+--------+--------+--------+-----+ |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to rx skb |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_process_event(bt_private *priv, struct sk_buff *skb) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| #ifdef DEBUG_LEVEL1 |
| struct m_dev *m_dev = &(priv->bt_dev.m_dev[BT_SEQ]); |
| #endif |
| BT_EVENT *pevent; |
| |
| ENTER(); |
| if (!m_dev) { |
| PRINTM(CMD, "BT: bt_process_event without m_dev\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pevent = (BT_EVENT *)skb->data; |
| if (pevent->EC != 0xff) { |
| PRINTM(CMD, "BT: Not Marvell Event=0x%x\n", pevent->EC); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| switch (pevent->data[0]) { |
| case BT_CMD_AUTO_SLEEP_MODE: |
| if (pevent->data[2] == BT_STATUS_SUCCESS) { |
| if (pevent->data[1] == BT_PS_ENABLE) |
| priv->adapter->psmode = 1; |
| else |
| priv->adapter->psmode = 0; |
| PRINTM(CMD, "BT: PS Mode %s:%s\n", m_dev->name, |
| (priv->adapter->psmode) ? "Enable" : "Disable"); |
| |
| } else { |
| PRINTM(CMD, "BT: PS Mode Command Fail %s\n", |
| m_dev->name); |
| } |
| break; |
| case BT_CMD_HOST_SLEEP_CONFIG: |
| if (pevent->data[3] == BT_STATUS_SUCCESS) { |
| PRINTM(CMD, "BT: %s: gpio=0x%x, gap=0x%x\n", |
| m_dev->name, pevent->data[1], pevent->data[2]); |
| } else { |
| PRINTM(CMD, "BT: %s: HSCFG Command Fail\n", |
| m_dev->name); |
| } |
| break; |
| case BT_CMD_HOST_SLEEP_ENABLE: |
| if (pevent->data[1] == BT_STATUS_SUCCESS) { |
| priv->adapter->hs_state = HS_ACTIVATED; |
| if (priv->adapter->suspend_fail == FALSE) { |
| #ifdef SDIO_SUSPEND_RESUME |
| #ifdef MMC_PM_KEEP_POWER |
| #ifdef MMC_PM_FUNC_SUSPENDED |
| bt_is_suspended(priv); |
| #endif |
| #endif |
| #endif |
| if (priv->adapter->wait_event_timeout) { |
| wake_up(&priv->adapter->cmd_wait_q); |
| priv->adapter->wait_event_timeout = |
| FALSE; |
| } else |
| wake_up_interruptible(&priv->adapter-> |
| cmd_wait_q); |
| |
| } |
| if (priv->adapter->psmode) |
| priv->adapter->ps_state = PS_SLEEP; |
| PRINTM(CMD, "BT: EVENT %s: HS ACTIVATED!\n", |
| m_dev->name); |
| |
| } else { |
| PRINTM(CMD, "BT: %s: HS Enable Fail\n", m_dev->name); |
| } |
| break; |
| case BT_CMD_MODULE_CFG_REQ: |
| if ((priv->bt_dev.sendcmdflag == TRUE) && |
| ((pevent->data[1] == MODULE_BRINGUP_REQ) |
| || (pevent->data[1] == MODULE_SHUTDOWN_REQ))) { |
| if (pevent->data[1] == MODULE_BRINGUP_REQ) { |
| PRINTM(CMD, "BT: EVENT %s:%s\n", m_dev->name, |
| (pevent->data[2] && (pevent->data[2] != |
| MODULE_CFG_RESP_ALREADY_UP)) |
| ? "Bring up Fail" : "Bring up success"); |
| priv->bt_dev.devType = pevent->data[3]; |
| PRINTM(CMD, "devType:%s\n", |
| (pevent->data[3] == |
| DEV_TYPE_AMP) ? "AMP controller" : |
| "BR/EDR controller"); |
| priv->bt_dev.devFeature = pevent->data[4]; |
| PRINTM(CMD, "devFeature: %s, %s, %s" |
| "\n", |
| ((pevent-> |
| data[4] & DEV_FEATURE_BT) ? |
| "BT Feature" : "No BT Feature"), |
| ((pevent-> |
| data[4] & DEV_FEATURE_BTAMP) ? |
| "BTAMP Feature" : "No BTAMP Feature"), |
| ((pevent-> |
| data[4] & DEV_FEATURE_BLE) ? |
| "BLE Feature" : "No BLE Feature") |
| ); |
| } |
| if (pevent->data[1] == MODULE_SHUTDOWN_REQ) { |
| PRINTM(CMD, "BT: EVENT %s:%s\n", m_dev->name, |
| (pevent->data[2]) ? "Shut down Fail" |
| : "Shut down success"); |
| |
| } |
| if (pevent->data[2]) { |
| priv->bt_dev.sendcmdflag = FALSE; |
| priv->adapter->cmd_complete = TRUE; |
| wake_up_interruptible(&priv->adapter-> |
| cmd_wait_q); |
| } |
| } else { |
| PRINTM(CMD, "BT_CMD_MODULE_CFG_REQ resp for APP\n"); |
| ret = BT_STATUS_FAILURE; |
| } |
| break; |
| case BT_EVENT_POWER_STATE: |
| if (pevent->data[1] == BT_PS_SLEEP) |
| priv->adapter->ps_state = PS_SLEEP; |
| if (priv->adapter->ps_state == PS_SLEEP |
| && (priv->card_type == CARD_TYPE_SD8887) |
| ) |
| os_sched_timeout(5); |
| PRINTM(CMD, "BT: EVENT %s:%s\n", m_dev->name, |
| (priv->adapter->ps_state) ? "PS_SLEEP" : "PS_AWAKE"); |
| |
| break; |
| case BT_CMD_SDIO_PULL_CFG_REQ: |
| if (pevent->data[pevent->length - 1] == BT_STATUS_SUCCESS) |
| PRINTM(CMD, "BT: %s: SDIO pull configuration success\n", |
| m_dev->name); |
| |
| else { |
| PRINTM(CMD, "BT: %s: SDIO pull configuration fail\n", |
| m_dev->name); |
| |
| } |
| break; |
| default: |
| PRINTM(CMD, "BT: Unknown Event=%d %s\n", pevent->data[0], |
| m_dev->name); |
| ret = BT_STATUS_FAILURE; |
| break; |
| } |
| exit: |
| if (ret == BT_STATUS_SUCCESS) |
| kfree_skb(skb); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function save the dump info to file |
| * |
| * @param dir_name directory name |
| * @param file_name file_name |
| * @param buf buffer |
| * @param buf_len buffer length |
| * |
| * @return 0 --success otherwise fail |
| */ |
| int |
| bt_save_dump_info_to_file(char *dir_name, char *file_name, u8 *buf, u32 buf_len) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| struct file *pfile = NULL; |
| u8 name[64]; |
| loff_t pos; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) |
| mm_segment_t fs; |
| #endif |
| |
| ENTER(); |
| |
| if (!dir_name || !file_name || !buf) { |
| PRINTM(ERROR, "Can't save dump info to file\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| |
| memset(name, 0, sizeof(name)); |
| snprintf((char *)name, sizeof(name), "%s/%s", dir_name, file_name); |
| pfile = filp_open((const char *)name, O_CREAT | O_RDWR, 0644); |
| if (IS_ERR(pfile)) { |
| PRINTM(MSG, |
| "Create file %s error, try to save dump file in /var\n", |
| name); |
| memset(name, 0, sizeof(name)); |
| snprintf((char *)name, sizeof(name), "%s/%s", "/var", |
| file_name); |
| pfile = filp_open((const char *)name, O_CREAT | O_RDWR, 0644); |
| } |
| if (IS_ERR(pfile)) { |
| PRINTM(ERROR, "Create Dump file for %s error\n", name); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| |
| PRINTM(MSG, "Dump data %s saved in %s\n", file_name, name); |
| |
| pos = 0; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| vfs_write(pfile, (const char __user *)buf, buf_len, &pos); |
| set_fs(fs); |
| #else |
| kernel_write(pfile, (const char __user *)buf, buf_len, &pos); |
| #endif |
| filp_close(pfile, NULL); |
| |
| PRINTM(MSG, "Dump data %s saved in %s successfully\n", file_name, name); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| #define DEBUG_HOST_READY 0xEE |
| #define DEBUG_FW_DONE 0xFF |
| #define DUMP_MAX_POLL_TRIES 200 |
| |
| #define DEBUG_DUMP_CTRL_REG_8897 0xE2 |
| #define DEBUG_DUMP_START_REG_8897 0xE3 |
| #define DEBUG_DUMP_END_REG_8897 0xEA |
| #define DEBUG_DUMP_CTRL_REG_8887 0xA2 |
| #define DEBUG_DUMP_START_REG_8887 0xA3 |
| #define DEBUG_DUMP_END_REG_8887 0xAA |
| #define DEBUG_DUMP_CTRL_REG_8977 0xF0 |
| #define DEBUG_DUMP_START_REG_8977 0xF1 |
| #define DEBUG_DUMP_END_REG_8977 0xF8 |
| #define DEBUG_DUMP_CTRL_REG_8978 0xF0 |
| #define DEBUG_DUMP_START_REG_8978 0xF1 |
| #define DEBUG_DUMP_END_REG_8978 0xF8 |
| #define DEBUG_DUMP_CTRL_REG_8997 0xF0 |
| #define DEBUG_DUMP_START_REG_8997 0xF1 |
| #define DEBUG_DUMP_END_REG_8997 0xF8 |
| #define DEBUG_DUMP_CTRL_REG_8987 0xF0 |
| #define DEBUG_DUMP_START_REG_8987 0xF1 |
| #define DEBUG_DUMP_END_REG_8987 0xF8 |
| |
| typedef enum { |
| DUMP_TYPE_ITCM = 0, |
| DUMP_TYPE_DTCM = 1, |
| DUMP_TYPE_SQRAM = 2, |
| DUMP_TYPE_APU_REGS = 3, |
| DUMP_TYPE_CIU_REGS = 4, |
| DUMP_TYPE_ICU_REGS = 5, |
| DUMP_TYPE_MAC_REGS = 6, |
| DUMP_TYPE_EXTEND_7 = 7, |
| DUMP_TYPE_EXTEND_8 = 8, |
| DUMP_TYPE_EXTEND_9 = 9, |
| DUMP_TYPE_EXTEND_10 = 10, |
| DUMP_TYPE_EXTEND_11 = 11, |
| DUMP_TYPE_EXTEND_12 = 12, |
| DUMP_TYPE_EXTEND_13 = 13, |
| DUMP_TYPE_EXTEND_LAST = 14 |
| } dumped_mem_type; |
| |
| #define MAX_NAME_LEN 8 |
| #define MAX_FULL_NAME_LEN 32 |
| |
| /** Memory type mapping structure */ |
| typedef struct { |
| /** memory name */ |
| u8 mem_name[MAX_NAME_LEN]; |
| /** memory pointer */ |
| u8 *mem_Ptr; |
| /** file structure */ |
| struct file *pfile_mem; |
| /** donbe flag */ |
| u8 done_flag; |
| /** dump type */ |
| u8 type; |
| } memory_type_mapping; |
| |
| memory_type_mapping bt_mem_type_mapping_tbl[] = { |
| {"ITCM", NULL, NULL, 0xF0, FW_DUMP_TYPE_MEM_ITCM}, |
| {"DTCM", NULL, NULL, 0xF1, FW_DUMP_TYPE_MEM_DTCM}, |
| {"SQRAM", NULL, NULL, 0xF2, FW_DUMP_TYPE_MEM_SQRAM}, |
| {"APU", NULL, NULL, 0xF3, FW_DUMP_TYPE_REG_APU}, |
| {"CIU", NULL, NULL, 0xF4, FW_DUMP_TYPE_REG_CIU}, |
| {"ICU", NULL, NULL, 0xF5, FW_DUMP_TYPE_REG_ICU}, |
| {"MAC", NULL, NULL, 0xF6, FW_DUMP_TYPE_REG_MAC}, |
| {"EXT7", NULL, NULL, 0xF7}, |
| {"EXT8", NULL, NULL, 0xF8}, |
| {"EXT9", NULL, NULL, 0xF9}, |
| {"EXT10", NULL, NULL, 0xFA}, |
| {"EXT11", NULL, NULL, 0xFB}, |
| {"EXT12", NULL, NULL, 0xFC}, |
| {"EXT13", NULL, NULL, 0xFD}, |
| {"EXTLAST", NULL, NULL, 0xFE}, |
| }; |
| |
| typedef enum { |
| RDWR_STATUS_SUCCESS = 0, |
| RDWR_STATUS_FAILURE = 1, |
| RDWR_STATUS_DONE = 2 |
| } rdwr_status; |
| |
| /** |
| * @brief This function read/write firmware via cmd52 |
| * |
| * @param phandle A pointer to moal_handle |
| * |
| * @return MLAN_STATUS_SUCCESS |
| */ |
| rdwr_status |
| bt_cmd52_rdwr_firmware(bt_private *priv, u8 doneflag) |
| { |
| int ret = 0; |
| int tries = 0; |
| u8 ctrl_data = 0; |
| u8 dbg_dump_ctrl_reg = 0; |
| |
| if (priv->card_type == CARD_TYPE_SD8887) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8887; |
| else if (priv->card_type == CARD_TYPE_SD8897) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8897; |
| else if (priv->card_type == CARD_TYPE_SD8977) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8977; |
| else if (priv->card_type == CARD_TYPE_SD8978) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8978; |
| else if (priv->card_type == CARD_TYPE_SD8997) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8997; |
| else if (priv->card_type == CARD_TYPE_SD8987) |
| dbg_dump_ctrl_reg = DEBUG_DUMP_CTRL_REG_8987; |
| |
| sdio_writeb(((struct sdio_mmc_card *)priv->bt_dev.card)->func, |
| DEBUG_HOST_READY, dbg_dump_ctrl_reg, &ret); |
| if (ret) { |
| PRINTM(ERROR, "SDIO Write ERR\n"); |
| return RDWR_STATUS_FAILURE; |
| } |
| for (tries = 0; tries < DUMP_MAX_POLL_TRIES; tries++) { |
| ctrl_data = |
| sdio_readb(((struct sdio_mmc_card *)priv->bt_dev.card)-> |
| func, dbg_dump_ctrl_reg, &ret); |
| if (ret) { |
| PRINTM(ERROR, "SDIO READ ERR\n"); |
| return RDWR_STATUS_FAILURE; |
| } |
| if (ctrl_data == DEBUG_FW_DONE) |
| break; |
| if (doneflag && ctrl_data == doneflag) |
| return RDWR_STATUS_DONE; |
| if (ctrl_data != DEBUG_HOST_READY) { |
| PRINTM(INFO, |
| "The ctrl reg was changed, re-try again!\n"); |
| sdio_writeb(((struct sdio_mmc_card *)priv->bt_dev. |
| card)->func, DEBUG_HOST_READY, |
| dbg_dump_ctrl_reg, &ret); |
| if (ret) { |
| PRINTM(ERROR, "SDIO Write ERR\n"); |
| return RDWR_STATUS_FAILURE; |
| } |
| } |
| udelay(100); |
| } |
| if (ctrl_data == DEBUG_HOST_READY) { |
| PRINTM(ERROR, "Fail to pull ctrl_data\n"); |
| return RDWR_STATUS_FAILURE; |
| } |
| |
| return RDWR_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function dump firmware memory to file |
| * |
| * @param phandle A pointer to moal_handle |
| * |
| * @return N/A |
| */ |
| void |
| bt_dump_firmware_info_v2(bt_private *priv) |
| { |
| int ret = 0; |
| unsigned int reg, reg_start, reg_end; |
| u8 *dbg_ptr = NULL; |
| u8 dump_num = 0; |
| u8 idx = 0; |
| u8 doneflag = 0; |
| rdwr_status stat; |
| u8 i = 0; |
| u8 read_reg = 0; |
| u32 memory_size = 0; |
| u8 path_name[64], file_name[32]; |
| u8 *end_ptr = NULL; |
| u8 dbg_dump_start_reg = 0; |
| u8 dbg_dump_end_reg = 0; |
| |
| if (!priv) { |
| PRINTM(ERROR, "Could not dump firmwware info\n"); |
| return; |
| } |
| |
| if ((priv->card_type != CARD_TYPE_SD8887) && |
| (priv->card_type != CARD_TYPE_SD8897) |
| && (priv->card_type != CARD_TYPE_SD8977) && |
| (priv->card_type != CARD_TYPE_SD8997) |
| && (priv->card_type != CARD_TYPE_SD8987) && |
| (priv->card_type != CARD_TYPE_SD8978)) { |
| PRINTM(MSG, "card_type %d don't support FW dump\n", |
| priv->card_type); |
| return; |
| } |
| |
| memset(path_name, 0, sizeof(path_name)); |
| strcpy((char *)path_name, "/data"); |
| PRINTM(MSG, "Create DUMP directory success:dir_name=%s\n", path_name); |
| |
| if (priv->card_type == CARD_TYPE_SD8887) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8887; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8887; |
| } else if (priv->card_type == CARD_TYPE_SD8897) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8897; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8897; |
| } else if (priv->card_type == CARD_TYPE_SD8977) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8977; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8977; |
| } else if (priv->card_type == CARD_TYPE_SD8978) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8978; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8978; |
| } else if (priv->card_type == CARD_TYPE_SD8997) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8997; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8997; |
| } else if (priv->card_type == CARD_TYPE_SD8987) { |
| dbg_dump_start_reg = DEBUG_DUMP_START_REG_8987; |
| dbg_dump_end_reg = DEBUG_DUMP_END_REG_8987; |
| } |
| |
| sbi_wakeup_firmware(priv); |
| sdio_claim_host(((struct sdio_mmc_card *)priv->bt_dev.card)->func); |
| priv->fw_dump = TRUE; |
| /* start dump fw memory */ |
| PRINTM(MSG, "==== DEBUG MODE OUTPUT START ====\n"); |
| /* read the number of the memories which will dump */ |
| if (RDWR_STATUS_FAILURE == bt_cmd52_rdwr_firmware(priv, doneflag)) |
| goto done; |
| reg = dbg_dump_start_reg; |
| dump_num = |
| sdio_readb(((struct sdio_mmc_card *)priv->bt_dev.card)->func, |
| reg, &ret); |
| if (ret) { |
| PRINTM(MSG, "SDIO READ MEM NUM ERR\n"); |
| goto done; |
| } |
| |
| /* read the length of every memory which will dump */ |
| for (idx = 0; idx < dump_num; idx++) { |
| if (RDWR_STATUS_FAILURE == |
| bt_cmd52_rdwr_firmware(priv, doneflag)) |
| goto done; |
| memory_size = 0; |
| reg = dbg_dump_start_reg; |
| for (i = 0; i < 4; i++) { |
| read_reg = |
| sdio_readb(((struct sdio_mmc_card *)priv-> |
| bt_dev.card)->func, reg, &ret); |
| if (ret) { |
| PRINTM(MSG, "SDIO READ ERR\n"); |
| goto done; |
| } |
| memory_size |= (read_reg << i * 8); |
| reg++; |
| } |
| if (memory_size == 0) { |
| PRINTM(MSG, "Firmware Dump Finished!\n"); |
| break; |
| } else { |
| PRINTM(MSG, "%s_SIZE=0x%x\n", |
| bt_mem_type_mapping_tbl[idx].mem_name, |
| memory_size); |
| bt_mem_type_mapping_tbl[idx].mem_Ptr = |
| vmalloc(memory_size + 1); |
| if ((ret != BT_STATUS_SUCCESS) || |
| !bt_mem_type_mapping_tbl[idx].mem_Ptr) { |
| PRINTM(ERROR, |
| "Error: vmalloc %s buffer failed!!!\n", |
| bt_mem_type_mapping_tbl[idx].mem_name); |
| goto done; |
| } |
| dbg_ptr = bt_mem_type_mapping_tbl[idx].mem_Ptr; |
| end_ptr = dbg_ptr + memory_size; |
| } |
| doneflag = bt_mem_type_mapping_tbl[idx].done_flag; |
| PRINTM(MSG, "Start %s output, please wait...\n", |
| bt_mem_type_mapping_tbl[idx].mem_name); |
| do { |
| stat = bt_cmd52_rdwr_firmware(priv, doneflag); |
| if (RDWR_STATUS_FAILURE == stat) |
| goto done; |
| |
| reg_start = dbg_dump_start_reg; |
| reg_end = dbg_dump_end_reg; |
| for (reg = reg_start; reg <= reg_end; reg++) { |
| *dbg_ptr = |
| sdio_readb(((struct sdio_mmc_card *) |
| priv->bt_dev.card)->func, |
| reg, &ret); |
| if (ret) { |
| PRINTM(MSG, "SDIO READ ERR\n"); |
| goto done; |
| } |
| if (dbg_ptr < end_ptr) |
| dbg_ptr++; |
| else |
| PRINTM(MSG, |
| "pre-allocced buf is not enough\n"); |
| } |
| if (RDWR_STATUS_DONE == stat) { |
| PRINTM(MSG, "%s done:" |
| "size = 0x%x\n", |
| bt_mem_type_mapping_tbl[idx].mem_name, |
| (unsigned int)(dbg_ptr - |
| bt_mem_type_mapping_tbl |
| [idx].mem_Ptr)); |
| memset(file_name, 0, sizeof(file_name)); |
| snprintf((char *)file_name, sizeof(file_name), |
| "%s%s", "file_bt_", |
| bt_mem_type_mapping_tbl[idx].mem_name); |
| if (BT_STATUS_SUCCESS != |
| bt_save_dump_info_to_file((char *)path_name, |
| (char *)file_name, |
| bt_mem_type_mapping_tbl |
| [idx].mem_Ptr, |
| memory_size)) |
| PRINTM(MSG, |
| "Can't save dump file %s in %s\n", |
| file_name, path_name); |
| vfree(bt_mem_type_mapping_tbl[idx].mem_Ptr); |
| bt_mem_type_mapping_tbl[idx].mem_Ptr = NULL; |
| break; |
| } |
| } while (1); |
| } |
| PRINTM(MSG, "==== DEBUG MODE OUTPUT END ====\n"); |
| /* end dump fw memory */ |
| done: |
| priv->fw_dump = FALSE; |
| sdio_release_host(((struct sdio_mmc_card *)priv->bt_dev.card)->func); |
| for (idx = 0; idx < dump_num; idx++) { |
| if (bt_mem_type_mapping_tbl[idx].mem_Ptr) { |
| vfree(bt_mem_type_mapping_tbl[idx].mem_Ptr); |
| bt_mem_type_mapping_tbl[idx].mem_Ptr = NULL; |
| } |
| } |
| PRINTM(MSG, "==== DEBUG MODE END ====\n"); |
| return; |
| } |
| |
| /** |
| * @brief This function shows debug info for timeout of command sending. |
| * |
| * @param adapter A pointer to bt_private |
| * @param cmd Timeout command id |
| * |
| * @return N/A |
| */ |
| static void |
| bt_cmd_timeout_func(bt_private *priv, u16 cmd) |
| { |
| bt_adapter *adapter = priv->adapter; |
| ENTER(); |
| |
| adapter->num_cmd_timeout++; |
| |
| PRINTM(ERROR, "Version = %s\n", adapter->drv_ver); |
| PRINTM(ERROR, "Timeout Command id = 0x%x\n", cmd); |
| PRINTM(ERROR, "Number of command timeout = %d\n", |
| adapter->num_cmd_timeout); |
| PRINTM(ERROR, "Interrupt counter = %d\n", adapter->IntCounter); |
| PRINTM(ERROR, "Power Save mode = %d\n", adapter->psmode); |
| PRINTM(ERROR, "Power Save state = %d\n", adapter->ps_state); |
| PRINTM(ERROR, "Host Sleep state = %d\n", adapter->hs_state); |
| PRINTM(ERROR, "hs skip count = %d\n", adapter->hs_skip); |
| PRINTM(ERROR, "suspend_fail flag = %d\n", adapter->suspend_fail); |
| PRINTM(ERROR, "suspended flag = %d\n", adapter->is_suspended); |
| PRINTM(ERROR, "Number of wakeup tries = %d\n", adapter->WakeupTries); |
| PRINTM(ERROR, "Host Cmd complet state = %d\n", adapter->cmd_complete); |
| PRINTM(ERROR, "Last irq recv = %d\n", adapter->irq_recv); |
| PRINTM(ERROR, "Last irq processed = %d\n", adapter->irq_done); |
| PRINTM(ERROR, "tx pending = %d\n", adapter->skb_pending); |
| PRINTM(ERROR, "sdio int status = %d\n", adapter->sd_ireg); |
| bt_dump_sdio_regs(priv); |
| LEAVE(); |
| } |
| |
| /** |
| * @brief This function queue frame |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return N/A |
| */ |
| static void |
| bt_queue_frame(bt_private *priv, struct sk_buff *skb) |
| { |
| skb_queue_tail(&priv->adapter->tx_queue, skb); |
| } |
| |
| /** |
| * @brief This function send reset cmd to firmware |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_send_reset_command(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_HCI_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_HCI_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_HCI_CMD *)skb->data; |
| pcmd->ocf_ogf = __cpu_to_le16((RESET_OGF << 10) | BT_CMD_RESET); |
| pcmd->length = 0x00; |
| pcmd->cmd_type = 0x00; |
| bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; |
| skb_put(skb, 3); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| PRINTM(CMD, "Queue Reset Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Reset timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_RESET); |
| } else { |
| PRINTM(CMD, "BT: Reset Command done\n"); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sends module cfg cmd to firmware |
| * |
| * Command format: |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * | OCF OGF | Length | Data | |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * | 2-byte | 1-byte | 4-byte | |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * |
| * @param priv A pointer to bt_private structure |
| * @param subcmd sub command |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_send_module_cfg_cmd(bt_private *priv, int subcmd) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "BT: No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_MODULE_CFG_REQ); |
| pcmd->length = 1; |
| pcmd->data[0] = subcmd; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| PRINTM(CMD, "Queue module cfg Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| /* |
| On some Android platforms certain delay is needed for HCI daemon to |
| remove this module and close itself gracefully. Otherwise it hangs. |
| This 10ms delay is a workaround for such platforms as the root cause |
| has not been found yet. */ |
| mdelay(10); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: module_cfg_cmd(%#x): timeout sendcmdflag=%d\n", |
| subcmd, priv->bt_dev.sendcmdflag); |
| bt_cmd_timeout_func(priv, BT_CMD_MODULE_CFG_REQ); |
| } else { |
| PRINTM(CMD, "BT: module cfg Command done\n"); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function enables power save mode |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_enable_ps(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_AUTO_SLEEP_MODE); |
| if (priv->bt_dev.psmode) |
| pcmd->data[0] = BT_PS_ENABLE; |
| else |
| pcmd->data[0] = BT_PS_DISABLE; |
| if (priv->bt_dev.idle_timeout) { |
| pcmd->length = 3; |
| pcmd->data[1] = (u8)(priv->bt_dev.idle_timeout & 0x00ff); |
| pcmd->data[2] = (priv->bt_dev.idle_timeout & 0xff00) >> 8; |
| } else { |
| pcmd->length = 1; |
| } |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue PSMODE Command(0x%x):%d\n", |
| __le16_to_cpu(pcmd->ocf_ogf), pcmd->data[0]); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: psmode timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_AUTO_SLEEP_MODE); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sends hscfg command |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_send_hscfg_cmd(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_HOST_SLEEP_CONFIG); |
| pcmd->length = 2; |
| pcmd->data[0] = (priv->bt_dev.gpio_gap & 0xff00) >> 8; |
| pcmd->data[1] = (u8)(priv->bt_dev.gpio_gap & 0x00ff); |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue HSCFG Command(0x%x),gpio=0x%x,gap=0x%x\n", |
| __le16_to_cpu(pcmd->ocf_ogf), pcmd->data[0], pcmd->data[1]); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: HSCFG timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_HOST_SLEEP_CONFIG); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sends sdio pull ctrl command |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_send_sdio_pull_ctrl_cmd(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_SDIO_PULL_CFG_REQ); |
| pcmd->length = 4; |
| pcmd->data[0] = (priv->bt_dev.sdio_pull_cfg & 0x000000ff); |
| pcmd->data[1] = (priv->bt_dev.sdio_pull_cfg & 0x0000ff00) >> 8; |
| pcmd->data[2] = (priv->bt_dev.sdio_pull_cfg & 0x00ff0000) >> 16; |
| pcmd->data[3] = (priv->bt_dev.sdio_pull_cfg & 0xff000000) >> 24; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, |
| "Queue SDIO PULL CFG Command(0x%x), PullUp=0x%x%x,PullDown=0x%x%x\n", |
| __le16_to_cpu(pcmd->ocf_ogf), pcmd->data[1], pcmd->data[0], |
| pcmd->data[3], pcmd->data[2]); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: SDIO PULL CFG timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_SDIO_PULL_CFG_REQ); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sends command to configure PMIC |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_pmic_configure(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_PMIC_CONFIGURE); |
| pcmd->length = 0; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue PMIC Configure Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: PMIC Configure timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_PMIC_CONFIGURE); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function enables host sleep |
| * |
| * @param priv A pointer to bt_private structure |
| * @param is_shutdown indicate shutdown mode |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_enable_hs(bt_private *priv, bool is_shutdown) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| priv->adapter->suspend_fail = FALSE; |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_HOST_SLEEP_ENABLE); |
| pcmd->length = 0; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->adapter->wait_event_timeout = is_shutdown; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| PRINTM(CMD, "Queue hs enable Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (is_shutdown) { |
| if (!os_wait_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->hs_state, |
| WAIT_UNTIL_HS_STATE_CHANGED)) { |
| PRINTM(MSG, "BT: Enable host sleep timeout:\n"); |
| priv->adapter->wait_event_timeout = FALSE; |
| bt_cmd_timeout_func(priv, BT_CMD_HOST_SLEEP_ENABLE); |
| } |
| } else { |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->hs_state, |
| WAIT_UNTIL_HS_STATE_CHANGED)) { |
| PRINTM(MSG, "BT: Enable host sleep timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_HOST_SLEEP_ENABLE); |
| } |
| } |
| OS_INT_DISABLE; |
| if ((priv->adapter->hs_state == HS_ACTIVATED) || |
| (priv->adapter->is_suspended == TRUE)) { |
| OS_INT_RESTORE; |
| PRINTM(MSG, "BT: suspend success! skip=%d\n", |
| priv->adapter->hs_skip); |
| } else { |
| priv->adapter->suspend_fail = TRUE; |
| OS_INT_RESTORE; |
| priv->adapter->hs_skip++; |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, |
| "BT: suspend skipped! " |
| "state=%d skip=%d ps_state= %d WakeupTries=%d\n", |
| priv->adapter->hs_state, priv->adapter->hs_skip, |
| priv->adapter->ps_state, priv->adapter->WakeupTries); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function Set Evt Filter |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_set_evt_filter(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = __cpu_to_le16((0x03 << 10) | BT_CMD_SET_EVT_FILTER); |
| pcmd->length = 0x03; |
| pcmd->data[0] = 0x02; |
| pcmd->data[1] = 0x00; |
| pcmd->data[2] = 0x03; |
| bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue Set Evt Filter Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout(priv->adapter->cmd_wait_q, |
| priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Set Evt Filter timeout\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_SET_EVT_FILTER); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function Enable Write Scan - Page and Inquiry |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_enable_write_scan(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = __cpu_to_le16((0x03 << 10) | BT_CMD_ENABLE_WRITE_SCAN); |
| pcmd->length = 0x01; |
| pcmd->data[0] = 0x03; |
| bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue Enable Write Scan Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout(priv->adapter->cmd_wait_q, |
| priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Enable Write Scan timeout\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_ENABLE_WRITE_SCAN); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function Enable Device under test mode |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_enable_device_under_testmode(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((0x06 << 10) | BT_CMD_ENABLE_DEVICE_TESTMODE); |
| pcmd->length = 0x00; |
| bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| PRINTM(CMD, "Queue enable device under testmode Command(0x%x)\n", |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout(priv->adapter->cmd_wait_q, |
| priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Enable Device under TEST mode timeout\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_ENABLE_DEVICE_TESTMODE); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function enables test mode and send cmd |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_enable_test_mode(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| |
| ENTER(); |
| |
| /** Set Evt Filter Command */ |
| ret = bt_set_evt_filter(priv); |
| if (ret != BT_STATUS_SUCCESS) { |
| PRINTM(ERROR, "BT test_mode: Set Evt filter fail\n"); |
| goto exit; |
| } |
| |
| /** Enable Write Scan Command */ |
| ret = bt_enable_write_scan(priv); |
| if (ret != BT_STATUS_SUCCESS) { |
| PRINTM(ERROR, "BT test_mode: Enable Write Scan fail\n"); |
| goto exit; |
| } |
| |
| /** Enable Device under test mode */ |
| ret = bt_enable_device_under_testmode(priv); |
| if (ret != BT_STATUS_SUCCESS) |
| PRINTM(ERROR, |
| "BT test_mode: Enable device under testmode fail\n"); |
| |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| #define DISABLE_RESET 0x0 |
| #define ENABLE_OUTBAND_RESET 0x1 |
| #define ENABLE_INBAND_RESET 0x02 |
| #define DEFAULT_GPIO 0xff |
| /** |
| * @brief This function set GPIO pin |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_set_independent_reset(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| u8 mode, gpio; |
| BT_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_INDEPENDENT_RESET); |
| mode = btindrst & 0xff; |
| gpio = (btindrst & 0xff00) >> 8; |
| if (mode == ENABLE_OUTBAND_RESET) { |
| pcmd->data[0] = ENABLE_OUTBAND_RESET; |
| if (!gpio) |
| pcmd->data[1] = DEFAULT_GPIO; |
| else |
| pcmd->data[1] = gpio; |
| } else if (mode == ENABLE_INBAND_RESET) { |
| pcmd->data[0] = ENABLE_INBAND_RESET; |
| pcmd->data[1] = DEFAULT_GPIO; |
| } else if (mode == DISABLE_RESET) { |
| pcmd->data[0] = DISABLE_RESET; |
| pcmd->data[1] = DEFAULT_GPIO; |
| } else { |
| PRINTM(WARN, "Unsupport mode\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| PRINTM(CMD, "BT: independant reset mode=%d gpio=%d\n", mode, gpio); |
| pcmd->length = 2; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout(priv->adapter->cmd_wait_q, |
| priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Independent reset : timeout!\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_INDEPENDENT_RESET); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sets ble deepsleep mode |
| * |
| * @param priv A pointer to bt_private structure |
| * @param mode TRUE/FALSE |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_set_ble_deepsleep(bt_private *priv, int mode) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_BLE_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_BLE_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_BLE_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_BLE_DEEP_SLEEP); |
| pcmd->length = 1; |
| pcmd->deepsleep = mode; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, sizeof(BT_BLE_CMD)); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| PRINTM(CMD, "BT: Set BLE deepsleep = %d (0x%x)\n", mode, |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Set BLE deepsleep timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_BLE_DEEP_SLEEP); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function gets FW version |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_get_fw_version(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_HCI_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_HCI_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_HCI_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_GET_FW_VERSION); |
| pcmd->length = 0x01; |
| pcmd->cmd_type = 0x00; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, 4); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout(priv->adapter->cmd_wait_q, |
| priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Get FW version: timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_GET_FW_VERSION); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function sets mac address |
| * |
| * @param priv A pointer to bt_private structure |
| * @param mac A pointer to mac address |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_set_mac_address(bt_private *priv, u8 *mac) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_HCI_CMD *pcmd; |
| int i = 0; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_HCI_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_HCI_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_CONFIG_MAC_ADDR); |
| pcmd->length = 8; |
| pcmd->cmd_type = MRVL_VENDOR_PKT; |
| pcmd->cmd_len = 6; |
| for (i = 0; i < 6; i++) |
| pcmd->data[i] = mac[5 - i]; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, sizeof(BT_HCI_CMD)); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| PRINTM(CMD, "BT: Set mac addr " MACSTR " (0x%x)\n", MAC2STR(mac), |
| __le16_to_cpu(pcmd->ocf_ogf)); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(MSG, "BT: Set mac addr: timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_CONFIG_MAC_ADDR); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function load the calibrate data |
| * |
| * @param priv A pointer to bt_private structure |
| * @param config_data A pointer to calibrate data |
| * @param mac A pointer to mac address |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_load_cal_data(bt_private *priv, u8 *config_data, u8 *mac) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| int i = 0; |
| /* u8 config_data[28] = {0x37 0x01 0x1c 0x00 0xFF 0xFF 0xFF 0xFF 0x01 |
| 0x7f 0x04 0x02 0x00 0x00 0xBA 0xCE 0xC0 0xC6 0x2D 0x00 0x00 0x00 |
| 0x00 0x00 0x00 0x00 0xF0}; */ |
| |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_LOAD_CONFIG_DATA); |
| pcmd->length = 0x20; |
| pcmd->data[0] = 0x00; |
| pcmd->data[1] = 0x00; |
| pcmd->data[2] = 0x00; |
| pcmd->data[3] = 0x1C; |
| /* swip cal-data byte */ |
| for (i = 4; i < 32; i++) |
| pcmd->data[i] = *(config_data + ((i / 4) * 8 - 1 - i)); |
| if (mac != NULL) { |
| pcmd->data[2] = 0x01; /* skip checksum */ |
| for (i = 24; i < 30; i++) |
| pcmd->data[i] = mac[29 - i]; |
| } |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| |
| DBG_HEXDUMP(DAT_D, "calirate data: ", pcmd->data, 32); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(ERROR, "BT: Load calibrate data: timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_LOAD_CONFIG_DATA); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function load the calibrate EXT data |
| * |
| * @param priv A pointer to bt_private structure |
| * @param config_data A pointer to calibrate data |
| * @param mac A pointer to mac address |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_load_cal_data_ext(bt_private *priv, u8 *config_data, u32 cfg_data_len) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CMD *pcmd; |
| |
| ENTER(); |
| |
| if (cfg_data_len > BT_CMD_DATA_LEN) { |
| PRINTM(WARN, "cfg_data_len is too long exceed %d.\n", |
| BT_CMD_DATA_LEN); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| |
| skb = bt_skb_alloc(sizeof(BT_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_LOAD_CONFIG_DATA_EXT); |
| pcmd->length = cfg_data_len; |
| |
| memcpy(pcmd->data, config_data, cfg_data_len); |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, BT_CMD_HEADER_SIZE + pcmd->length); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| |
| DBG_HEXDUMP(DAT_D, "calirate ext data", pcmd->data, pcmd->length); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(ERROR, "BT: Load calibrate ext data: timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_LOAD_CONFIG_DATA_EXT); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function writes value to CSU registers |
| * |
| * @param priv A pointer to bt_private structure |
| * @param type reg type |
| * @param offset register address |
| * @param value register value to write |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_write_reg(bt_private *priv, u8 type, u32 offset, u16 value) |
| { |
| struct sk_buff *skb = NULL; |
| int ret = BT_STATUS_SUCCESS; |
| BT_CSU_CMD *pcmd; |
| ENTER(); |
| skb = bt_skb_alloc(sizeof(BT_CSU_CMD), GFP_ATOMIC); |
| if (skb == NULL) { |
| PRINTM(WARN, "No free skb\n"); |
| ret = BT_STATUS_FAILURE; |
| goto exit; |
| } |
| pcmd = (BT_CSU_CMD *)skb->data; |
| pcmd->ocf_ogf = |
| __cpu_to_le16((VENDOR_OGF << 10) | BT_CMD_CSU_WRITE_REG); |
| pcmd->length = 7; |
| pcmd->type = type; |
| pcmd->offset[0] = (offset & 0x000000ff); |
| pcmd->offset[1] = (offset & 0x0000ff00) >> 8; |
| pcmd->offset[2] = (offset & 0x00ff0000) >> 16; |
| pcmd->offset[3] = (offset & 0xff000000) >> 24; |
| pcmd->value[0] = (value & 0x00ff); |
| pcmd->value[1] = (value & 0xff00) >> 8; |
| bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; |
| skb_put(skb, sizeof(BT_CSU_CMD)); |
| skb->dev = (void *)(&(priv->bt_dev.m_dev[BT_SEQ])); |
| skb_queue_head(&priv->adapter->tx_queue, skb); |
| priv->bt_dev.sendcmdflag = TRUE; |
| priv->bt_dev.send_cmd_opcode = __le16_to_cpu(pcmd->ocf_ogf); |
| priv->adapter->cmd_complete = FALSE; |
| PRINTM(CMD, "BT: Set CSU reg type=%d reg=0x%x value=0x%x\n", |
| type, offset, value); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| if (!os_wait_interruptible_timeout |
| (priv->adapter->cmd_wait_q, priv->adapter->cmd_complete, |
| WAIT_UNTIL_CMD_RESP)) { |
| ret = BT_STATUS_FAILURE; |
| PRINTM(ERROR, "BT: Set CSU reg timeout:\n"); |
| bt_cmd_timeout_func(priv, BT_CMD_CSU_WRITE_REG); |
| } |
| exit: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function used to restore tx_queue |
| * |
| * @param priv A pointer to bt_private structure |
| * @return N/A |
| */ |
| void |
| bt_restore_tx_queue(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| while (!skb_queue_empty(&priv->adapter->pending_queue)) { |
| skb = skb_dequeue(&priv->adapter->pending_queue); |
| if (skb) |
| bt_queue_frame(priv, skb); |
| } |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| } |
| |
| /** |
| * @brief This function used to send command to firmware |
| * |
| * Command format: |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * | OCF OGF | Length | Data | |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * | 2-byte | 1-byte | 4-byte | |
| * +--------+--------+--------+--------+--------+--------+--------+ |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| bt_prepare_command(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| ENTER(); |
| if (priv->bt_dev.hscfgcmd) { |
| priv->bt_dev.hscfgcmd = 0; |
| ret = bt_send_hscfg_cmd(priv); |
| } |
| if (priv->bt_dev.pscmd) { |
| priv->bt_dev.pscmd = 0; |
| ret = bt_enable_ps(priv); |
| } |
| if (priv->bt_dev.sdio_pull_ctrl) { |
| priv->bt_dev.sdio_pull_ctrl = 0; |
| ret = bt_send_sdio_pull_ctrl_cmd(priv); |
| } |
| if (priv->bt_dev.hscmd) { |
| priv->bt_dev.hscmd = 0; |
| if (priv->bt_dev.hsmode) |
| ret = bt_enable_hs(priv, FALSE); |
| else { |
| ret = sbi_wakeup_firmware(priv); |
| priv->adapter->hs_state = HS_DEACTIVATED; |
| } |
| } |
| if (priv->bt_dev.test_mode) { |
| priv->bt_dev.test_mode = 0; |
| ret = bt_enable_test_mode(priv); |
| } |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief Update tx state |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return N/A |
| */ |
| static void |
| update_stat_byte_tx(bt_private *priv, struct sk_buff *skb) |
| { |
| ((struct hci_dev *)priv->bt_dev.m_dev[BT_SEQ].dev_pointer)->stat. |
| byte_tx += skb->len; |
| } |
| |
| /** |
| * @brief Update tx error state |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return N/A |
| */ |
| static void |
| update_stat_err_tx(bt_private *priv, struct sk_buff *skb) |
| { |
| ((struct hci_dev *)priv->bt_dev.m_dev[BT_SEQ].dev_pointer)->stat. |
| err_tx++; |
| } |
| |
| /** @brief This function processes a single packet |
| * |
| * @param priv A pointer to bt_private structure |
| * @param skb A pointer to skb which includes TX packet |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| static int |
| send_single_packet(bt_private *priv, struct sk_buff *skb) |
| { |
| int ret; |
| struct sk_buff *new_skb = NULL; |
| ENTER(); |
| if (!skb || !skb->data) { |
| LEAVE(); |
| return BT_STATUS_FAILURE; |
| } |
| |
| if (!skb->len || ((skb->len + BT_HEADER_LEN) > BT_UPLD_SIZE)) { |
| PRINTM(ERROR, "Tx Error: Bad skb length %d : %d\n", skb->len, |
| BT_UPLD_SIZE); |
| LEAVE(); |
| return BT_STATUS_FAILURE; |
| } |
| if (skb_headroom(skb) < BT_HEADER_LEN) { |
| new_skb = skb_realloc_headroom(skb, BT_HEADER_LEN); |
| if (!new_skb) { |
| PRINTM(ERROR, "TX error: realloc_headroom failed %d\n", |
| BT_HEADER_LEN); |
| kfree_skb(skb); |
| LEAVE(); |
| return BT_STATUS_FAILURE; |
| } else { |
| if (new_skb != skb) |
| dev_kfree_skb_any(skb); |
| skb = new_skb; |
| } |
| } |
| /* This is SDIO/PCIE specific header length: byte[3][2][1], * type: |
| byte[0] (HCI_COMMAND = 1, ACL_DATA = 2, SCO_DATA = 3, 0xFE = Vendor) |
| */ |
| skb_push(skb, BT_HEADER_LEN); |
| skb->data[0] = (skb->len & 0x0000ff); |
| skb->data[1] = (skb->len & 0x00ff00) >> 8; |
| skb->data[2] = (skb->len & 0xff0000) >> 16; |
| skb->data[3] = bt_cb(skb)->pkt_type; |
| if (bt_cb(skb)->pkt_type == MRVL_VENDOR_PKT) |
| PRINTM(CMD, "DNLD_CMD: ocf_ogf=0x%x len=%d\n", |
| __le16_to_cpu(*((u16 *) & skb->data[4])), skb->len); |
| ret = sbi_host_to_card(priv, skb->data, skb->len); |
| if (ret == BT_STATUS_FAILURE) |
| update_stat_err_tx(priv, skb); |
| else |
| update_stat_byte_tx(priv, skb); |
| if (ret != BT_STATUS_PENDING) |
| kfree_skb(skb); |
| LEAVE(); |
| return ret; |
| } |
| |
| #ifdef CONFIG_OF |
| /** |
| * @brief This function read the initial parameter from device tress |
| * |
| * |
| * @return N/A |
| */ |
| static void |
| bt_init_from_dev_tree(void) |
| { |
| struct device_node *dt_node = NULL; |
| struct property *prop; |
| u32 data; |
| const char *string_data; |
| |
| ENTER(); |
| |
| if (!dts_enable) { |
| PRINTM(CMD, "DTS is disabled!"); |
| return; |
| } |
| |
| dt_node = of_find_node_by_name(NULL, "sd8xxx-bt"); |
| if (!dt_node) { |
| LEAVE(); |
| return; |
| } |
| for_each_property_of_node(dt_node, prop) { |
| #ifdef DEBUG_LEVEL1 |
| if (!strncmp(prop->name, "mbt_drvdbg", strlen("mbt_drvdbg"))) { |
| if (!of_property_read_u32(dt_node, prop->name, &data)) { |
| PRINTM(CMD, "mbt_drvdbg=0x%x\n", data); |
| mbt_drvdbg = data; |
| } |
| } |
| #endif |
| else if (!strncmp(prop->name, "init_cfg", strlen("init_cfg"))) { |
| if (!of_property_read_string |
| (dt_node, prop->name, &string_data)) { |
| init_cfg = (char *)string_data; |
| PRINTM(CMD, "init_cfg=%s\n", init_cfg); |
| } |
| } else if (!strncmp |
| (prop->name, "cal_cfg_ext", strlen("cal_cfg_ext"))) { |
| if (!of_property_read_string |
| (dt_node, prop->name, &string_data)) { |
| cal_cfg_ext = (char *)string_data; |
| PRINTM(CMD, "cal_cfg_ext=%s\n", cal_cfg_ext); |
| } |
| } else if (!strncmp(prop->name, "cal_cfg", strlen("cal_cfg"))) { |
| if (!of_property_read_string |
| (dt_node, prop->name, &string_data)) { |
| cal_cfg = (char *)string_data; |
| PRINTM(CMD, "cal_cfg=%s\n", cal_cfg); |
| } |
| } else if (!strncmp(prop->name, "bt_mac", strlen("bt_mac"))) { |
| if (!of_property_read_string |
| (dt_node, prop->name, &string_data)) { |
| bt_mac = (char *)string_data; |
| PRINTM(CMD, "bt_mac=%s\n", bt_mac); |
| } |
| } else if (!strncmp(prop->name, "btindrst", strlen("btindrst"))) { |
| if (!of_property_read_u32(dt_node, prop->name, &data)) { |
| btindrst = data; |
| PRINTM(CMD, "btindrst=%d\n", btindrst); |
| } |
| } else if (!strncmp(prop->name, "btpmic", strlen("btpmic"))) { |
| if (!of_property_read_u32(dt_node, prop->name, &data)) { |
| btpmic = data; |
| PRINTM(CMD, "btpmic=%d\n", btpmic); |
| } |
| } |
| } |
| LEAVE(); |
| return; |
| } |
| #endif |
| |
| /** |
| * @brief This function initializes the adapter structure |
| * and set default value to the member of adapter. |
| * |
| * @param priv A pointer to bt_private structure |
| * @return N/A |
| */ |
| static void |
| bt_init_adapter(bt_private *priv) |
| { |
| ENTER(); |
| #ifdef CONFIG_OF |
| bt_init_from_dev_tree(); |
| #endif |
| skb_queue_head_init(&priv->adapter->tx_queue); |
| skb_queue_head_init(&priv->adapter->pending_queue); |
| priv->adapter->tx_lock = FALSE; |
| priv->adapter->ps_state = PS_AWAKE; |
| priv->adapter->suspend_fail = FALSE; |
| priv->adapter->is_suspended = FALSE; |
| priv->adapter->hs_skip = 0; |
| priv->adapter->num_cmd_timeout = 0; |
| priv->adapter->fwdump_fname = NULL; |
| init_waitqueue_head(&priv->adapter->cmd_wait_q); |
| LEAVE(); |
| } |
| |
| /** |
| * @brief This function initializes firmware |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| static int |
| bt_init_fw(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| ENTER(); |
| if (fw == 0) { |
| sbi_enable_host_int(priv); |
| goto done; |
| } |
| sbi_disable_host_int(priv); |
| if ((priv->card_type == CARD_TYPE_SD8787) || |
| (priv->card_type == CARD_TYPE_SD8777)) |
| priv->fw_crc_check = fw_crc_check; |
| if (sbi_download_fw(priv)) { |
| PRINTM(ERROR, " FW failed to be download!\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| #define FW_POLL_TRIES 100 |
| #define SD8897_FW_RESET_REG 0x0E8 |
| #define SD8887_FW_RESET_REG 0x0B6 |
| #define SD8977_SD8978_SD8997_FW_RESET_REG 0x0EE |
| #define SD8887_SD8897_FW_RESET_VAL 1 |
| #define SD8977_SD8978_SD8997_FW_RESET_VAL 0x99 |
| |
| /** |
| * @brief This function reload firmware |
| * |
| * @param priv A pointer to bt_private |
| * @param mode FW reload mode |
| * |
| * @return 0--success, otherwise failure |
| */ |
| static int |
| bt_reload_fw(bt_private *priv, int mode) |
| { |
| int ret = 0, tries = 0; |
| u8 value = 1; |
| u32 reset_reg = 0; |
| u8 reset_val = 0; |
| |
| ENTER(); |
| if ((mode != FW_RELOAD_SDIO_INBAND_RESET) && |
| (mode != FW_RELOAD_NO_EMULATION)) { |
| PRINTM(ERROR, "Invalid fw reload mode=%d\n", mode); |
| return -EFAULT; |
| } |
| |
| /** flush pending tx_queue */ |
| skb_queue_purge(&priv->adapter->tx_queue); |
| if (mode == FW_RELOAD_SDIO_INBAND_RESET) { |
| if (priv->card_type == CARD_TYPE_SD8887) { |
| reset_reg = SD8887_FW_RESET_REG; |
| reset_val = SD8887_SD8897_FW_RESET_VAL; |
| } else if (priv->card_type == CARD_TYPE_SD8897) { |
| reset_reg = SD8897_FW_RESET_REG; |
| reset_val = SD8887_SD8897_FW_RESET_VAL; |
| } else if ((priv->card_type == CARD_TYPE_SD8977) || |
| (priv->card_type == CARD_TYPE_SD8997) || |
| (priv->card_type == CARD_TYPE_SD8978) || |
| (priv->card_type == CARD_TYPE_SD8987)) { |
| reset_reg = SD8977_SD8978_SD8997_FW_RESET_REG; |
| reset_val = SD8977_SD8978_SD8997_FW_RESET_VAL; |
| } |
| sbi_disable_host_int(priv); |
| /** Wake up firmware firstly */ |
| sbi_wakeup_firmware(priv); |
| |
| /** wait SOC fully wake up */ |
| for (tries = 0; tries < FW_POLL_TRIES; ++tries) { |
| ret = sd_write_reg(priv, reset_reg, 0xba); |
| if (!ret) { |
| ret = sd_read_reg(priv, reset_reg, &value); |
| if (!ret && (value == 0xba)) { |
| PRINTM(MSG, "Fw wake up\n"); |
| break; |
| } |
| } |
| udelay(1000); |
| } |
| |
| ret = sd_write_reg(priv, reset_reg, reset_val); |
| if (ret) { |
| PRINTM(ERROR, "Failed to write register.\n"); |
| goto done; |
| } |
| |
| /** Poll register around 1 ms */ |
| for (; tries < FW_POLL_TRIES; ++tries) { |
| ret = sd_read_reg(priv, reset_reg, &value); |
| if (ret) { |
| PRINTM(ERROR, "Failed to read register.\n"); |
| goto done; |
| } |
| if (value == 0) |
| /** FW is ready */ |
| break; |
| udelay(1000); |
| } |
| if (value) { |
| PRINTM(ERROR, |
| "Failed to poll FW reset register %X=0x%x\n", |
| reset_reg, value); |
| ret = -EFAULT; |
| goto done; |
| } |
| } |
| |
| sbi_enable_host_int(priv); |
| /** reload FW */ |
| ret = bt_init_fw(priv); |
| if (ret) { |
| PRINTM(ERROR, "Re download firmware failed.\n"); |
| ret = -EFAULT; |
| } |
| LEAVE(); |
| return ret; |
| done: |
| sbi_enable_host_int(priv); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function request to reload firmware |
| * |
| * @param priv A pointer to bt_private |
| * @param mode fw reload mode. |
| * |
| * @return N/A |
| */ |
| void |
| bt_request_fw_reload(bt_private *priv, int mode) |
| { |
| ENTER(); |
| if (mode == FW_RELOAD_WITH_EMULATION) { |
| bt_fw_reload = FW_RELOAD_WITH_EMULATION; |
| PRINTM(MSG, "BT: FW reload with re-emulation...\n"); |
| LEAVE(); |
| return; |
| } |
| /** Reload FW */ |
| priv->fw_reload = TRUE; |
| if (bt_reload_fw(priv, mode)) { |
| PRINTM(ERROR, "FW reload fail\n"); |
| goto done; |
| } |
| priv->fw_reload = FALSE; |
| /** Other operation here? */ |
| done: |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief This function frees the structure of adapter |
| * |
| * @param priv A pointer to bt_private structure |
| * @return N/A |
| */ |
| void |
| bt_free_adapter(bt_private *priv) |
| { |
| bt_adapter *adapter = priv->adapter; |
| ENTER(); |
| skb_queue_purge(&priv->adapter->tx_queue); |
| kfree(adapter->tx_buffer); |
| kfree(adapter->hw_regs_buf); |
| /* Free allocated memory for fwdump filename */ |
| if (adapter->fwdump_fname) { |
| kfree(adapter->fwdump_fname); |
| adapter->fwdump_fname = NULL; |
| } |
| /* Free the adapter object itself */ |
| kfree(adapter); |
| priv->adapter = NULL; |
| |
| LEAVE(); |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| /** |
| * @brief This function handles the BT ioctl |
| * |
| * @param hdev A pointer to hci_dev structure |
| * @cmd ioctl cmd |
| * @arg argument |
| * @return -ENOIOCTLCMD |
| */ |
| static int |
| bt_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) |
| { |
| ENTER(); |
| LEAVE(); |
| return -ENOIOCTLCMD; |
| } |
| #endif |
| |
| /** |
| * @brief This function handles the wrapper_dev ioctl |
| * |
| * @param hev A pointer to wrapper_dev structure |
| * @cmd ioctl cmd |
| * @arg argument |
| * @return -ENOIOCTLCMD |
| */ |
| static int |
| mdev_ioctl(struct m_dev *m_dev, unsigned int cmd, void *arg) |
| { |
| bt_private *priv = NULL; |
| int ret = 0; |
| |
| ENTER(); |
| |
| if (!m_dev || !m_dev->driver_data) { |
| PRINTM(ERROR, "Ioctl for unknown device (m_dev=NULL)\n"); |
| ret = -ENODEV; |
| goto done; |
| } |
| priv = (bt_private *)m_dev->driver_data; |
| if (!test_bit(HCI_RUNNING, &m_dev->flags)) { |
| PRINTM(ERROR, "HCI_RUNNING not set, flag=0x%lx\n", |
| m_dev->flags); |
| ret = -EBUSY; |
| goto done; |
| } |
| PRINTM(INFO, "IOCTL: cmd=%d\n", cmd); |
| switch (cmd) { |
| default: |
| break; |
| } |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function handles BT destruct |
| * |
| * @param hdev A pointer to hci_dev structure |
| * |
| * @return N/A |
| */ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) |
| static void |
| bt_destruct(struct hci_dev *hdev) |
| { |
| ENTER(); |
| LEAVE(); |
| return; |
| } |
| #endif |
| |
| /** |
| * @brief This function handles wrapper device destruct |
| * |
| * @param m_dev A pointer to m_dev structure |
| * |
| * @return N/A |
| */ |
| static void |
| mdev_destruct(struct m_dev *m_dev) |
| { |
| ENTER(); |
| LEAVE(); |
| return; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| /** |
| * @brief This function handles the BT transmit |
| * |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return BT_STATUS_SUCCESS or other error no. |
| */ |
| static int |
| bt_send_frame(struct sk_buff *skb) |
| #else |
| /** |
| * @brief This function handles the BT transmit |
| * |
| * @param hdev A pointer to hci_dev structure |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return BT_STATUS_SUCCESS or other error no. |
| */ |
| static int |
| bt_send_frame(struct hci_dev *hdev, struct sk_buff *skb) |
| #endif |
| { |
| bt_private *priv = NULL; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| struct hci_dev *hdev = (struct hci_dev *)skb->dev; |
| #else |
| skb->dev = (void *)hdev; |
| #endif |
| |
| ENTER(); |
| PRINTM(DATA, "bt_send_frame %s: Type=%d, len=%d\n", hdev->name, |
| bt_cb(skb)->pkt_type, skb->len); |
| DBG_HEXDUMP(CMD_D, "bt_send_frame", skb->data, skb->len); |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) |
| if (!hdev || !hci_get_drvdata(hdev)) { |
| #else |
| if (!hdev || !hdev->driver_data) { |
| #endif |
| PRINTM(ERROR, "Frame for unknown HCI device (hdev=NULL)\n"); |
| LEAVE(); |
| return -ENODEV; |
| } |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) |
| priv = (bt_private *)hci_get_drvdata(hdev); |
| #else |
| priv = (bt_private *)hdev->driver_data; |
| #endif |
| if (!test_bit(HCI_RUNNING, &hdev->flags)) { |
| PRINTM(ERROR, "Fail test HCI_RUNNING, flag=0x%lx\n", |
| hdev->flags); |
| LEAVE(); |
| return -EBUSY; |
| } |
| switch (bt_cb(skb)->pkt_type) { |
| case HCI_COMMAND_PKT: |
| hdev->stat.cmd_tx++; |
| break; |
| case HCI_ACLDATA_PKT: |
| hdev->stat.acl_tx++; |
| break; |
| case HCI_SCODATA_PKT: |
| hdev->stat.sco_tx++; |
| break; |
| } |
| if (priv->adapter->tx_lock == TRUE) |
| skb_queue_tail(&priv->adapter->pending_queue, skb); |
| else |
| bt_queue_frame(priv, skb); |
| |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function handles the wrapper device transmit |
| * |
| * @param m_dev A pointer to m_dev structure |
| * @param skb A pointer to sk_buff structure |
| * |
| * @return BT_STATUS_SUCCESS or other error no. |
| */ |
| static int |
| mdev_send_frame(struct m_dev *m_dev, struct sk_buff *skb) |
| { |
| bt_private *priv = NULL; |
| |
| ENTER(); |
| if (!m_dev || !m_dev->driver_data) { |
| PRINTM(ERROR, "Frame for unknown HCI device (m_dev=NULL)\n"); |
| LEAVE(); |
| return -ENODEV; |
| } |
| priv = (bt_private *)m_dev->driver_data; |
| if (!test_bit(HCI_RUNNING, &m_dev->flags)) { |
| PRINTM(ERROR, "Fail test HCI_RUNNING, flag=0x%lx\n", |
| m_dev->flags); |
| LEAVE(); |
| return -EBUSY; |
| } |
| switch (bt_cb(skb)->pkt_type) { |
| case HCI_COMMAND_PKT: |
| m_dev->stat.cmd_tx++; |
| break; |
| case HCI_ACLDATA_PKT: |
| m_dev->stat.acl_tx++; |
| break; |
| case HCI_SCODATA_PKT: |
| m_dev->stat.sco_tx++; |
| break; |
| } |
| |
| if (priv->adapter->tx_lock == TRUE) |
| skb_queue_tail(&priv->adapter->pending_queue, skb); |
| else |
| bt_queue_frame(priv, skb); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function flushes the transmit queue |
| * |
| * @param hdev A pointer to hci_dev structure |
| * |
| * @return BT_STATUS_SUCCESS |
| */ |
| static int |
| bt_flush(struct hci_dev *hdev) |
| { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) |
| bt_private *priv = (bt_private *)hci_get_drvdata(hdev); |
| #else |
| bt_private *priv = (bt_private *)hdev->driver_data; |
| #endif |
| ENTER(); |
| skb_queue_purge(&priv->adapter->tx_queue); |
| skb_queue_purge(&priv->adapter->pending_queue); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function flushes the transmit queue |
| * |
| * @param m_dev A pointer to m_dev structure |
| * |
| * @return BT_STATUS_SUCCESS |
| */ |
| static int |
| mdev_flush(struct m_dev *m_dev) |
| { |
| bt_private *priv = (bt_private *)m_dev->driver_data; |
| ENTER(); |
| skb_queue_purge(&priv->adapter->tx_queue); |
| skb_queue_purge(&priv->adapter->pending_queue); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function closes the bluetooth device |
| * |
| * @param hdev A pointer to hci_dev structure |
| * |
| * @return BT_STATUS_SUCCESS |
| */ |
| static int |
| bt_close(struct hci_dev *hdev) |
| { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) |
| bt_private *priv = (bt_private *)hci_get_drvdata(hdev); |
| #else |
| bt_private *priv = (bt_private *)hdev->driver_data; |
| #endif |
| |
| ENTER(); |
| #if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 3, 0) |
| if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) { |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| #endif |
| skb_queue_purge(&priv->adapter->tx_queue); |
| |
| module_put(THIS_MODULE); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function closes the wrapper device |
| * |
| * @param m_dev A pointer to m_dev structure |
| * |
| * @return BT_STATUS_SUCCESS |
| */ |
| static int |
| mdev_close(struct m_dev *m_dev) |
| { |
| |
| ENTER(); |
| mdev_req_lock(m_dev); |
| if (!test_and_clear_bit(HCI_UP, &m_dev->flags)) { |
| mdev_req_unlock(m_dev); |
| LEAVE(); |
| return 0; |
| } |
| |
| if (m_dev->flush) |
| m_dev->flush(m_dev); |
| /* wait up pending read and unregister char dev */ |
| wake_up_interruptible(&m_dev->req_wait_q); |
| /* Drop queues */ |
| skb_queue_purge(&m_dev->rx_q); |
| if (!test_and_clear_bit(HCI_RUNNING, &m_dev->flags)) { |
| mdev_req_unlock(m_dev); |
| LEAVE(); |
| return 0; |
| } |
| module_put(THIS_MODULE); |
| m_dev->flags = 0; |
| mdev_req_unlock(m_dev); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function opens the bluetooth device |
| * |
| * @param hdev A pointer to hci_dev structure |
| * |
| * @return BT_STATUS_SUCCESS or other |
| */ |
| static int |
| bt_open(struct hci_dev *hdev) |
| { |
| ENTER(); |
| if (try_module_get(THIS_MODULE) == 0) |
| return BT_STATUS_FAILURE; |
| #if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 3, 0) |
| set_bit(HCI_RUNNING, &hdev->flags); |
| #endif |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function opens the wrapper device |
| * |
| * @param m_dev A pointer to m_dev structure |
| * |
| * @return BT_STATUS_SUCCESS or other |
| */ |
| static int |
| mdev_open(struct m_dev *m_dev) |
| { |
| ENTER(); |
| |
| if (try_module_get(THIS_MODULE) == 0) |
| return BT_STATUS_FAILURE; |
| |
| set_bit(HCI_RUNNING, &m_dev->flags); |
| |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function queries the wrapper device |
| * |
| * @param m_dev A pointer to m_dev structure |
| * @param arg arguement |
| * |
| * @return BT_STATUS_SUCCESS or other |
| */ |
| void |
| mdev_query(struct m_dev *m_dev, void *arg) |
| { |
| struct mbt_dev *mbt_dev = (struct mbt_dev *)m_dev->dev_pointer; |
| |
| ENTER(); |
| if (copy_to_user(arg, &mbt_dev->type, sizeof(mbt_dev->type))) |
| PRINTM(ERROR, "IOCTL_QUERY_TYPE: Fail copy to user\n"); |
| |
| LEAVE(); |
| } |
| |
| /** |
| * @brief This function initializes the wrapper device |
| * |
| * @param m_dev A pointer to m_dev structure |
| * |
| * @return BT_STATUS_SUCCESS or other |
| */ |
| void |
| init_m_dev(struct m_dev *m_dev) |
| { |
| m_dev->dev_pointer = NULL; |
| m_dev->driver_data = NULL; |
| m_dev->dev_type = 0; |
| m_dev->spec_type = 0; |
| skb_queue_head_init(&m_dev->rx_q); |
| init_waitqueue_head(&m_dev->req_wait_q); |
| init_waitqueue_head(&m_dev->rx_wait_q); |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) |
| init_MUTEX(&m_dev->req_lock); |
| #else |
| sema_init(&m_dev->req_lock, 1); |
| #endif |
| spin_lock_init(&m_dev->rxlock); |
| memset(&m_dev->stat, 0, sizeof(struct hci_dev_stats)); |
| m_dev->open = mdev_open; |
| m_dev->close = mdev_close; |
| m_dev->flush = mdev_flush; |
| m_dev->send = mdev_send_frame; |
| m_dev->destruct = mdev_destruct; |
| m_dev->ioctl = mdev_ioctl; |
| m_dev->query = mdev_query; |
| m_dev->owner = THIS_MODULE; |
| |
| } |
| |
| /** |
| * @brief This function handles the major job in bluetooth driver. |
| * it handles the event generated by firmware, rx data received |
| * from firmware and tx data sent from kernel. |
| * |
| * @param data A pointer to bt_thread structure |
| * @return BT_STATUS_SUCCESS |
| */ |
| static int |
| bt_service_main_thread(void *data) |
| { |
| bt_thread *thread = data; |
| bt_private *priv = thread->priv; |
| bt_adapter *adapter = priv->adapter; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) |
| wait_queue_t wait; |
| #else |
| wait_queue_entry_t wait; |
| #endif |
| struct sk_buff *skb; |
| ENTER(); |
| bt_activate_thread(thread); |
| init_waitqueue_entry(&wait, current); |
| current->flags |= PF_NOFREEZE; |
| |
| for (;;) { |
| add_wait_queue(&thread->waitQ, &wait); |
| OS_SET_THREAD_STATE(TASK_INTERRUPTIBLE); |
| if (priv->adapter->WakeupTries || |
| ((!priv->adapter->IntCounter) && |
| (!priv->bt_dev.tx_dnld_rdy || |
| skb_queue_empty(&priv->adapter->tx_queue)) |
| )) { |
| PRINTM(INFO, "Main: Thread sleeping...\n"); |
| schedule(); |
| } |
| OS_SET_THREAD_STATE(TASK_RUNNING); |
| remove_wait_queue(&thread->waitQ, &wait); |
| if (kthread_should_stop() || adapter->SurpriseRemoved) { |
| PRINTM(INFO, "main-thread: break from main thread: " |
| "SurpriseRemoved=0x%x\n", |
| adapter->SurpriseRemoved); |
| break; |
| } |
| |
| PRINTM(INFO, "Main: Thread waking up...\n"); |
| |
| if (priv->adapter->IntCounter) { |
| OS_INT_DISABLE; |
| adapter->IntCounter = 0; |
| OS_INT_RESTORE; |
| sbi_get_int_status(priv); |
| } else if ((priv->adapter->ps_state == PS_SLEEP) && |
| (!skb_queue_empty(&priv->adapter->tx_queue) |
| )) { |
| priv->adapter->WakeupTries++; |
| sbi_wakeup_firmware(priv); |
| continue; |
| } |
| if (priv->adapter->ps_state == PS_SLEEP) |
| continue; |
| if (priv->bt_dev.tx_dnld_rdy == TRUE) { |
| if (!skb_queue_empty(&priv->adapter->tx_queue)) { |
| skb = skb_dequeue(&priv->adapter->tx_queue); |
| if (skb) |
| send_single_packet(priv, skb); |
| } |
| } |
| } |
| bt_deactivate_thread(thread); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function handles the interrupt. it will change PS |
| * state if applicable. it will wake up main_thread to handle |
| * the interrupt event as well. |
| * |
| * @param m_dev A pointer to m_dev structure |
| * @return N/A |
| */ |
| void |
| bt_interrupt(struct m_dev *m_dev) |
| { |
| bt_private *priv = (bt_private *)m_dev->driver_data; |
| ENTER(); |
| if (!priv || !priv->adapter) { |
| LEAVE(); |
| return; |
| } |
| PRINTM(INTR, "*\n"); |
| priv->adapter->ps_state = PS_AWAKE; |
| if (priv->adapter->hs_state == HS_ACTIVATED) { |
| PRINTM(CMD, "BT: %s: HS DEACTIVATED in ISR!\n", m_dev->name); |
| priv->adapter->hs_state = HS_DEACTIVATED; |
| } |
| priv->adapter->WakeupTries = 0; |
| priv->adapter->IntCounter++; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| LEAVE(); |
| } |
| |
| /** |
| * @brief Dynamic release of bt private |
| * |
| * @param kobj A pointer to kobject structure |
| * |
| * @return N/A |
| */ |
| static void |
| bt_private_dynamic_release(struct kobject *kobj) |
| { |
| bt_private *priv = container_of(kobj, bt_private, kobj); |
| ENTER(); |
| PRINTM(INFO, "free bt priv\n"); |
| kfree(priv); |
| LEAVE(); |
| } |
| |
| static struct kobj_type ktype_bt_private_dynamic = { |
| .release = bt_private_dynamic_release, |
| }; |
| |
| /** |
| * @brief Allocation of bt private |
| * |
| * @param N/A |
| * |
| * @return bt_private |
| */ |
| static bt_private * |
| bt_alloc_priv(void) |
| { |
| bt_private *priv; |
| ENTER(); |
| priv = kzalloc(sizeof(bt_private), GFP_KERNEL); |
| if (priv) { |
| kobject_init(&priv->kobj, &ktype_bt_private_dynamic); |
| PRINTM(INFO, "alloc bt priv\n"); |
| } |
| LEAVE(); |
| return priv; |
| } |
| |
| /** |
| * @brief Get bt private structure |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return kobject structure |
| */ |
| struct kobject * |
| bt_priv_get(bt_private *priv) |
| { |
| PRINTM(INFO, "bt priv get object"); |
| return kobject_get(&priv->kobj); |
| } |
| |
| /** |
| * @brief Get bt private structure |
| * |
| * @param priv A pointer to bt_private structure |
| * |
| * @return N/A |
| */ |
| void |
| bt_priv_put(bt_private *priv) |
| { |
| PRINTM(INFO, "bt priv put object"); |
| kobject_put(&priv->kobj); |
| } |
| |
| /** |
| * @brief This function send init commands to firmware |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCESS/BT_STATUS_FAILURE |
| */ |
| int |
| bt_init_cmd(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| |
| ENTER(); |
| |
| ret = bt_send_module_cfg_cmd(priv, MODULE_BRINGUP_REQ); |
| if (ret < 0) { |
| PRINTM(FATAL, "Module cfg command send failed!\n"); |
| goto done; |
| } |
| if (btindrst != -1) { |
| ret = bt_set_independent_reset(priv); |
| if (ret < 0) { |
| PRINTM(FATAL, "Independent reset failed!\n"); |
| goto done; |
| } |
| } |
| if (btpmic |
| && ((priv->card_type == CARD_TYPE_SD8997) || |
| (priv->card_type == CARD_TYPE_SD8977) || |
| (priv->card_type == CARD_TYPE_SD8978)) |
| ) { |
| if (BT_STATUS_SUCCESS != bt_pmic_configure(priv)) { |
| PRINTM(FATAL, "BT: PMIC Configure failed \n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| ret = bt_set_ble_deepsleep(priv, deep_sleep ? TRUE : FALSE); |
| if (ret < 0) { |
| PRINTM(FATAL, "%s BLE deepsleep failed!\n", |
| deep_sleep ? "Enable" : "Disable"); |
| goto done; |
| } |
| if (psmode) { |
| priv->bt_dev.psmode = TRUE; |
| priv->bt_dev.idle_timeout = DEFAULT_IDLE_TIME; |
| ret = bt_enable_ps(priv); |
| if (ret < 0) { |
| PRINTM(FATAL, "Enable PS mode failed!\n"); |
| goto done; |
| } |
| } |
| #if defined(SDIO_SUSPEND_RESUME) |
| priv->bt_dev.gpio_gap = DEF_GPIO_GAP; |
| ret = bt_send_hscfg_cmd(priv); |
| if (ret < 0) { |
| PRINTM(FATAL, "Send HSCFG failed!\n"); |
| goto done; |
| } |
| #endif |
| priv->bt_dev.sdio_pull_cfg = 0xffffffff; |
| priv->bt_dev.sdio_pull_ctrl = 0; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| |
| done: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function reinit firmware after redownload firmware |
| * |
| * @param priv A pointer to bt_private structure |
| * @return BT_STATUS_SUCESS/BT_STATUS_FAILURE |
| */ |
| int |
| bt_reinit_fw(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| priv->adapter->tx_lock = FALSE; |
| priv->adapter->ps_state = PS_AWAKE; |
| priv->adapter->suspend_fail = FALSE; |
| priv->adapter->is_suspended = FALSE; |
| priv->adapter->hs_skip = 0; |
| priv->adapter->num_cmd_timeout = 0; |
| |
| ret = bt_init_cmd(priv); |
| if (ret < 0) { |
| PRINTM(FATAL, "BT init command failed!\n"); |
| goto done; |
| } |
| /* block all the packet from bluez */ |
| if (init_cfg || cal_cfg || bt_mac || cal_cfg_ext) |
| priv->adapter->tx_lock = TRUE; |
| |
| if (init_cfg) |
| if (BT_STATUS_SUCCESS != bt_init_config(priv, init_cfg)) { |
| PRINTM(FATAL, |
| "BT: Set user init data and param failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| |
| if (cal_cfg) { |
| if (BT_STATUS_SUCCESS != bt_cal_config(priv, cal_cfg, bt_mac)) { |
| PRINTM(FATAL, "BT: Set cal data failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| |
| if (bt_mac) { |
| PRINTM(INFO, |
| "Set BT mac_addr from insmod parametre bt_mac = %s\n", |
| bt_mac); |
| if (BT_STATUS_SUCCESS != bt_init_mac_address(priv, bt_mac)) { |
| PRINTM(FATAL, |
| "BT: Fail to set mac address from insmod parametre\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| |
| if (cal_cfg_ext) { |
| if (BT_STATUS_SUCCESS != bt_cal_config_ext(priv, cal_cfg_ext)) { |
| PRINTM(FATAL, "BT: Set cal ext data failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| if (init_cfg || cal_cfg || bt_mac || cal_cfg_ext) { |
| priv->adapter->tx_lock = FALSE; |
| bt_restore_tx_queue(priv); |
| } |
| bt_get_fw_version(priv); |
| snprintf((char *)priv->adapter->drv_ver, MAX_VER_STR_LEN, |
| mbt_driver_version, fw_version); |
| done: |
| return ret; |
| } |
| |
| /** |
| * @brief Module configuration and register device |
| * |
| * @param priv A Pointer to bt_private structure |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| int |
| sbi_register_conf_dpc(bt_private *priv) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| struct hci_dev *hdev = NULL; |
| unsigned char dev_type = 0; |
| |
| ENTER(); |
| |
| priv->bt_dev.tx_dnld_rdy = TRUE; |
| if (priv->fw_reload) { |
| bt_reinit_fw(priv); |
| LEAVE(); |
| return ret; |
| } |
| |
| if (drv_mode & DRV_MODE_BT) { |
| hdev = hci_alloc_dev(); |
| if (!hdev) { |
| PRINTM(FATAL, "Can not allocate HCI device\n"); |
| ret = -ENOMEM; |
| goto err_kmalloc; |
| } |
| hdev->open = bt_open; |
| hdev->close = bt_close; |
| hdev->flush = bt_flush; |
| hdev->send = bt_send_frame; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) |
| hdev->destruct = bt_destruct; |
| #endif |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| hdev->ioctl = bt_ioctl; |
| #endif |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) |
| hci_set_drvdata(hdev, priv); |
| #else |
| hdev->driver_data = priv; |
| #endif |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) |
| hdev->owner = THIS_MODULE; |
| #endif |
| priv->bt_dev.m_dev[BT_SEQ].dev_type = BT_TYPE; |
| priv->bt_dev.m_dev[BT_SEQ].spec_type = BLUEZ_SPEC; |
| priv->bt_dev.m_dev[BT_SEQ].dev_pointer = (void *)hdev; |
| priv->bt_dev.m_dev[BT_SEQ].driver_data = priv; |
| priv->bt_dev.m_dev[BT_SEQ].read_continue_flag = 0; |
| } |
| |
| dev_type = HCI_SDIO; |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) |
| if (hdev) |
| hdev->bus = dev_type; |
| #else |
| if (hdev) |
| hdev->type = dev_type; |
| #endif /* >= 2.6.34 */ |
| |
| ret = bt_init_cmd(priv); |
| if (ret < 0) { |
| PRINTM(FATAL, "BT init command failed!\n"); |
| goto done; |
| } |
| |
| /** Process device tree init parameters before register hci device. |
| * Since uplayer device has not yet registered, no need to block tx queue. |
| * */ |
| if (init_cfg) |
| if (BT_STATUS_SUCCESS != bt_init_config(priv, init_cfg)) { |
| PRINTM(FATAL, |
| "BT: Set user init data and param failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| if (cal_cfg) { |
| if (BT_STATUS_SUCCESS != bt_cal_config(priv, cal_cfg, bt_mac)) { |
| PRINTM(FATAL, "BT: Set cal data failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } else if (bt_mac) { |
| PRINTM(INFO, |
| "Set BT mac_addr from insmod parametre bt_mac = %s\n", |
| bt_mac); |
| if (BT_STATUS_SUCCESS != bt_init_mac_address(priv, bt_mac)) { |
| PRINTM(FATAL, |
| "BT: Fail to set mac address from insmod parametre\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| if (cal_cfg_ext) { |
| if (BT_STATUS_SUCCESS != bt_cal_config_ext(priv, cal_cfg_ext)) { |
| PRINTM(FATAL, "BT: Set cal ext data failed\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| } |
| |
| /* Get FW version */ |
| bt_get_fw_version(priv); |
| snprintf((char *)priv->adapter->drv_ver, MAX_VER_STR_LEN, |
| mbt_driver_version, fw_version); |
| |
| if (hdev) { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) |
| hdev->dev_type = priv->bt_dev.devType; |
| #endif |
| ret = hci_register_dev(hdev); |
| if (ret < 0) { |
| PRINTM(FATAL, "Can not register HCI device\n"); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| snprintf((char *)priv->bt_dev.m_dev[BT_SEQ].name, |
| sizeof(priv->bt_dev.m_dev[BT_SEQ].name), hdev->name); |
| bt_proc_init(priv, &(priv->bt_dev.m_dev[BT_SEQ]), BT_SEQ); |
| } |
| |
| done: |
| LEAVE(); |
| return ret; |
| err_kmalloc: |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function adds the card. it will probe the |
| * card, allocate the bt_priv and initialize the device. |
| * |
| * @param card A pointer to card |
| * @return A pointer to bt_private structure |
| */ |
| |
| bt_private * |
| bt_add_card(void *card) |
| { |
| bt_private *priv = NULL; |
| int index = 0; |
| |
| ENTER(); |
| |
| priv = bt_alloc_priv(); |
| if (!priv) { |
| PRINTM(FATAL, "Can not allocate priv\n"); |
| LEAVE(); |
| return NULL; |
| } |
| /* Save the handle */ |
| for (index = 0; index < MAX_BT_ADAPTER; index++) { |
| if (m_priv[index] == NULL) |
| break; |
| } |
| if (index < MAX_BT_ADAPTER) { |
| m_priv[index] = priv; |
| } else { |
| PRINTM(ERROR, "Exceeded maximum cards supported!\n"); |
| goto err_kmalloc; |
| } |
| /* allocate buffer for bt_adapter */ |
| priv->adapter = kzalloc(sizeof(bt_adapter), GFP_KERNEL); |
| if (!priv->adapter) { |
| PRINTM(FATAL, "Allocate buffer for bt_adapter failed!\n"); |
| goto err_kmalloc; |
| } |
| priv->adapter->tx_buffer = |
| kzalloc(MAX_TX_BUF_SIZE + DMA_ALIGNMENT, GFP_KERNEL); |
| if (!priv->adapter->tx_buffer) { |
| PRINTM(FATAL, "Allocate buffer for transmit\n"); |
| goto err_kmalloc; |
| } |
| priv->adapter->tx_buf = |
| (u8 *)ALIGN_ADDR(priv->adapter->tx_buffer, DMA_ALIGNMENT); |
| priv->adapter->hw_regs_buf = |
| kzalloc(SD_BLOCK_SIZE + DMA_ALIGNMENT, GFP_KERNEL); |
| if (!priv->adapter->hw_regs_buf) { |
| PRINTM(FATAL, "Allocate buffer for INT read buf failed!\n"); |
| goto err_kmalloc; |
| } |
| priv->adapter->hw_regs = |
| (u8 *)ALIGN_ADDR(priv->adapter->hw_regs_buf, DMA_ALIGNMENT); |
| bt_init_adapter(priv); |
| |
| PRINTM(INFO, "Starting kthread...\n"); |
| priv->MainThread.priv = priv; |
| spin_lock_init(&priv->driver_lock); |
| |
| bt_create_thread(bt_service_main_thread, &priv->MainThread, |
| "bt_main_service"); |
| |
| /* wait for mainthread to up */ |
| while (!priv->MainThread.pid) |
| os_sched_timeout(1); |
| |
| sdio_update_card_type(priv, card); |
| /* Update driver version */ |
| if (priv->card_type == CARD_TYPE_SD8787) |
| memcpy(mbt_driver_version, CARD_SD8787, strlen(CARD_SD8787)); |
| else if (priv->card_type == CARD_TYPE_SD8777) |
| memcpy(mbt_driver_version, CARD_SD8777, strlen(CARD_SD8777)); |
| else if (priv->card_type == CARD_TYPE_SD8887) |
| memcpy(mbt_driver_version, CARD_SD8887, strlen(CARD_SD8887)); |
| else if (priv->card_type == CARD_TYPE_SD8897) |
| memcpy(mbt_driver_version, CARD_SD8897, strlen(CARD_SD8897)); |
| else if (priv->card_type == CARD_TYPE_SD8797) |
| memcpy(mbt_driver_version, CARD_SD8797, strlen(CARD_SD8797)); |
| else if (priv->card_type == CARD_TYPE_SD8977) |
| memcpy(mbt_driver_version, CARD_SD8977, strlen(CARD_SD8977)); |
| else if (priv->card_type == CARD_TYPE_SD8978) |
| memcpy(mbt_driver_version, CARD_SD8978, strlen(CARD_SD8978)); |
| else if (priv->card_type == CARD_TYPE_SD8997) |
| memcpy(mbt_driver_version, CARD_SD8997, strlen(CARD_SD8997)); |
| else if (priv->card_type == CARD_TYPE_SD8987) |
| memcpy(mbt_driver_version, CARD_SD8987, strlen(CARD_SD8987)); |
| |
| if (BT_STATUS_SUCCESS != sdio_get_sdio_device(priv)) |
| goto err_kmalloc; |
| |
| /** user config file */ |
| init_waitqueue_head(&priv->init_user_conf_wait_q); |
| |
| priv->bt_dev.card = card; |
| |
| ((struct sdio_mmc_card *)card)->priv = priv; |
| priv->adapter->sd_ireg = 0; |
| /* |
| * Register the device. Fillup the private data structure with |
| * relevant information from the card and request for the required |
| * IRQ. |
| */ |
| if (sbi_register_dev(priv) < 0) { |
| PRINTM(FATAL, "Failed to register bt device!\n"); |
| goto err_registerdev; |
| } |
| if (bt_init_fw(priv)) { |
| PRINTM(FATAL, "BT Firmware Init Failed\n"); |
| goto err_init_fw; |
| } |
| LEAVE(); |
| return priv; |
| |
| err_init_fw: |
| clean_up_m_devs(priv); |
| bt_proc_remove(priv); |
| PRINTM(INFO, "Unregister device\n"); |
| sbi_unregister_dev(priv); |
| err_registerdev: |
| ((struct sdio_mmc_card *)card)->priv = NULL; |
| /* Stop the thread servicing the interrupts */ |
| priv->adapter->SurpriseRemoved = TRUE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| while (priv->MainThread.pid) |
| os_sched_timeout(1); |
| err_kmalloc: |
| if (priv->adapter) |
| bt_free_adapter(priv); |
| for (index = 0; index < MAX_BT_ADAPTER; index++) { |
| if (m_priv[index] == priv) { |
| m_priv[index] = NULL; |
| break; |
| } |
| } |
| bt_priv_put(priv); |
| LEAVE(); |
| return NULL; |
| } |
| |
| /** |
| * @brief This function send hardware remove event |
| * |
| * @param priv A pointer to bt_private |
| * @return N/A |
| */ |
| void |
| bt_send_hw_remove_event(bt_private *priv) |
| { |
| struct sk_buff *skb = NULL; |
| struct hci_dev *hdev = NULL; |
| ENTER(); |
| if (!priv->bt_dev.m_dev[BT_SEQ].dev_pointer) { |
| LEAVE(); |
| return; |
| } |
| if (priv->bt_dev.m_dev[BT_SEQ].spec_type == BLUEZ_SPEC) |
| hdev = (struct hci_dev *)priv->bt_dev.m_dev[BT_SEQ].dev_pointer; |
| #define HCI_HARDWARE_ERROR_EVT 0x10 |
| #define HCI_HARDWARE_REMOVE 0x24 |
| skb = bt_skb_alloc(3, GFP_ATOMIC); |
| skb->data[0] = HCI_HARDWARE_ERROR_EVT; |
| skb->data[1] = 1; |
| skb->data[2] = HCI_HARDWARE_REMOVE; |
| bt_cb(skb)->pkt_type = HCI_EVENT_PKT; |
| skb_put(skb, 3); |
| if (hdev) { |
| skb->dev = (void *)hdev; |
| PRINTM(MSG, "Send HW ERROR event\n"); |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) |
| hci_recv_frame(skb); |
| #else |
| hci_recv_frame(hdev, skb); |
| #endif |
| os_sched_timeout(5); |
| hdev->stat.byte_rx += 3; |
| } |
| LEAVE(); |
| return; |
| } |
| |
| /** |
| * @brief This function removes the card. |
| * |
| * @param card A pointer to card |
| * @return BT_STATUS_SUCCESS |
| */ |
| int |
| bt_remove_card(void *card) |
| { |
| bt_private *priv = (bt_private *)card; |
| int index; |
| ENTER(); |
| if (!priv) { |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| priv->adapter->SurpriseRemoved = TRUE; |
| |
| bt_send_hw_remove_event(priv); |
| wake_up_interruptible(&priv->adapter->cmd_wait_q); |
| priv->adapter->SurpriseRemoved = TRUE; |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| while (priv->MainThread.pid) { |
| os_sched_timeout(1); |
| wake_up_interruptible(&priv->MainThread.waitQ); |
| } |
| |
| bt_proc_remove(priv); |
| PRINTM(INFO, "Unregister device\n"); |
| sbi_unregister_dev(priv); |
| clean_up_m_devs(priv); |
| PRINTM(INFO, "Free Adapter\n"); |
| bt_free_adapter(priv); |
| for (index = 0; index < MAX_BT_ADAPTER; index++) { |
| if (m_priv[index] == priv) { |
| m_priv[index] = NULL; |
| break; |
| } |
| } |
| bt_priv_put(priv); |
| LEAVE(); |
| return BT_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief This function initializes module. |
| * |
| * @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE |
| */ |
| static int |
| bt_init_module(void) |
| { |
| int ret = BT_STATUS_SUCCESS; |
| int index; |
| ENTER(); |
| PRINTM(MSG, "BT: Loading driver\n"); |
| /* Init the bt_private pointer array first */ |
| for (index = 0; index < MAX_BT_ADAPTER; index++) |
| m_priv[index] = NULL; |
| bt_root_proc_init(); |
| |
| /** create char device class */ |
| chardev_class = class_create(THIS_MODULE, MODULE_NAME); |
| if (IS_ERR(chardev_class)) { |
| PRINTM(ERROR, "Unable to allocate class\n"); |
| bt_root_proc_remove(); |
| ret = PTR_ERR(chardev_class); |
| goto done; |
| } |
| |
| if (sbi_register() == NULL) { |
| bt_root_proc_remove(); |
| ret = BT_STATUS_FAILURE; |
| goto done; |
| } |
| done: |
| if (ret) |
| PRINTM(MSG, "BT: Driver loading failed\n"); |
| else |
| PRINTM(MSG, "BT: Driver loaded successfully\n"); |
| |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function cleans module |
| * |
| * @return N/A |
| */ |
| static void |
| bt_exit_module(void) |
| { |
| bt_private *priv; |
| int index; |
| ENTER(); |
| PRINTM(MSG, "BT: Unloading driver\n"); |
| for (index = 0; index < MAX_BT_ADAPTER; index++) { |
| priv = m_priv[index]; |
| if (!priv) |
| continue; |
| if (priv && !priv->adapter->SurpriseRemoved) { |
| if (BT_STATUS_SUCCESS == bt_send_reset_command(priv)) |
| bt_send_module_cfg_cmd(priv, |
| MODULE_SHUTDOWN_REQ); |
| } |
| sbi_disable_host_int(priv); |
| |
| } |
| |
| sbi_unregister(); |
| |
| bt_root_proc_remove(); |
| class_destroy(chardev_class); |
| PRINTM(MSG, "BT: Driver unloaded\n"); |
| LEAVE(); |
| } |
| |
| module_init(bt_init_module); |
| module_exit(bt_exit_module); |
| |
| MODULE_AUTHOR("Marvell International Ltd."); |
| MODULE_DESCRIPTION("Marvell Bluetooth Driver Ver. " VERSION); |
| MODULE_VERSION(VERSION); |
| MODULE_LICENSE("GPL"); |
| module_param(fw, int, 0); |
| MODULE_PARM_DESC(fw, "0: Skip firmware download; otherwise: Download firmware"); |
| module_param(fw_crc_check, int, 0); |
| MODULE_PARM_DESC(fw_crc_check, |
| "1: Enable FW download CRC check (default); 0: Disable FW download CRC check"); |
| module_param(psmode, int, 0); |
| MODULE_PARM_DESC(psmode, "1: Enable powermode; 0: Disable powermode"); |
| module_param(deep_sleep, int, 0); |
| MODULE_PARM_DESC(deep_sleep, "1: Enable deep sleep; 0: Disable deep sleep"); |
| #ifdef CONFIG_OF |
| module_param(dts_enable, int, 0); |
| MODULE_PARM_DESC(dts_enable, "0: Disable DTS; 1: Enable DTS"); |
| #endif |
| #ifdef DEBUG_LEVEL1 |
| module_param(mbt_drvdbg, uint, 0); |
| MODULE_PARM_DESC(mbt_drvdbg, "BIT3:DBG_DATA BIT4:DBG_CMD 0xFF:DBG_ALL"); |
| #endif |
| #ifdef SDIO_SUSPEND_RESUME |
| module_param(mbt_pm_keep_power, int, 0); |
| MODULE_PARM_DESC(mbt_pm_keep_power, "1: PM keep power; 0: PM no power"); |
| #endif |
| module_param(init_cfg, charp, 0); |
| MODULE_PARM_DESC(init_cfg, "BT init config file name"); |
| module_param(cal_cfg, charp, 0); |
| MODULE_PARM_DESC(cal_cfg, "BT calibrate file name"); |
| module_param(cal_cfg_ext, charp, 0); |
| MODULE_PARM_DESC(cal_cfg_ext, "BT calibrate ext file name"); |
| module_param(bt_mac, charp, 0660); |
| MODULE_PARM_DESC(bt_mac, "BT init mac address"); |
| module_param(drv_mode, int, 0); |
| MODULE_PARM_DESC(drv_mode, "Bit 0: BT/AMP/BLE;"); |
| module_param(bt_fw_reload, int, 0); |
| MODULE_PARM_DESC(bt_fw_reload, |
| "0: disable fw_reload; 1: enable fw reload feature"); |
| module_param(btindrst, int, 0); |
| MODULE_PARM_DESC(btindrst, |
| "Independent reset configuration; high byte:GPIO pin number;low byte:0x0:disable, 0x1:out-band reset, 0x2:in-band reset."); |
| module_param(btpmic, int, 0); |
| MODULE_PARM_DESC(btpmic, |
| "1: Send pmic configure cmd to firmware; 0: No pmic configure cmd sent to firmware (default)"); |
| module_param(bt_fw_serial, int, 0); |
| MODULE_PARM_DESC(bt_fw_serial, |
| "0: Support parallel download FW; 1: Support serial download FW"); |