forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
11 KiB
445 lines
11 KiB
/* |
|
* Copyright (c) 2014 Redpine Signals Inc. |
|
* |
|
* Permission to use, copy, modify, and/or distribute this software for any |
|
* purpose with or without fee is hereby granted, provided that the above |
|
* copyright notice and this permission notice appear in all copies. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/module.h> |
|
#include <linux/firmware.h> |
|
#include <net/rsi_91x.h> |
|
#include "rsi_mgmt.h" |
|
#include "rsi_common.h" |
|
#include "rsi_coex.h" |
|
#include "rsi_hal.h" |
|
|
|
u32 rsi_zone_enabled = /* INFO_ZONE | |
|
INIT_ZONE | |
|
MGMT_TX_ZONE | |
|
MGMT_RX_ZONE | |
|
DATA_TX_ZONE | |
|
DATA_RX_ZONE | |
|
FSM_ZONE | |
|
ISR_ZONE | */ |
|
ERR_ZONE | |
|
0; |
|
EXPORT_SYMBOL_GPL(rsi_zone_enabled); |
|
|
|
#ifdef CONFIG_RSI_COEX |
|
static struct rsi_proto_ops g_proto_ops = { |
|
.coex_send_pkt = rsi_coex_send_pkt, |
|
.get_host_intf = rsi_get_host_intf, |
|
.set_bt_context = rsi_set_bt_context, |
|
}; |
|
#endif |
|
|
|
/** |
|
* rsi_dbg() - This function outputs informational messages. |
|
* @zone: Zone of interest for output message. |
|
* @fmt: printf-style format for output message. |
|
* |
|
* Return: none |
|
*/ |
|
void rsi_dbg(u32 zone, const char *fmt, ...) |
|
{ |
|
struct va_format vaf; |
|
va_list args; |
|
|
|
va_start(args, fmt); |
|
|
|
vaf.fmt = fmt; |
|
vaf.va = &args; |
|
|
|
if (zone & rsi_zone_enabled) |
|
pr_info("%pV", &vaf); |
|
va_end(args); |
|
} |
|
EXPORT_SYMBOL_GPL(rsi_dbg); |
|
|
|
static char *opmode_str(int oper_mode) |
|
{ |
|
switch (oper_mode) { |
|
case DEV_OPMODE_WIFI_ALONE: |
|
return "Wi-Fi alone"; |
|
case DEV_OPMODE_BT_ALONE: |
|
return "BT EDR alone"; |
|
case DEV_OPMODE_BT_LE_ALONE: |
|
return "BT LE alone"; |
|
case DEV_OPMODE_BT_DUAL: |
|
return "BT Dual"; |
|
case DEV_OPMODE_STA_BT: |
|
return "Wi-Fi STA + BT EDR"; |
|
case DEV_OPMODE_STA_BT_LE: |
|
return "Wi-Fi STA + BT LE"; |
|
case DEV_OPMODE_STA_BT_DUAL: |
|
return "Wi-Fi STA + BT DUAL"; |
|
case DEV_OPMODE_AP_BT: |
|
return "Wi-Fi AP + BT EDR"; |
|
case DEV_OPMODE_AP_BT_DUAL: |
|
return "Wi-Fi AP + BT DUAL"; |
|
} |
|
|
|
return "Unknown"; |
|
} |
|
|
|
void rsi_print_version(struct rsi_common *common) |
|
{ |
|
rsi_dbg(ERR_ZONE, "================================================\n"); |
|
rsi_dbg(ERR_ZONE, "================ RSI Version Info ==============\n"); |
|
rsi_dbg(ERR_ZONE, "================================================\n"); |
|
rsi_dbg(ERR_ZONE, "FW Version\t: %d.%d.%d\n", |
|
common->lmac_ver.major, common->lmac_ver.minor, |
|
common->lmac_ver.release_num); |
|
rsi_dbg(ERR_ZONE, "Operating mode\t: %d [%s]", |
|
common->oper_mode, opmode_str(common->oper_mode)); |
|
rsi_dbg(ERR_ZONE, "Firmware file\t: %s", common->priv->fw_file_name); |
|
rsi_dbg(ERR_ZONE, "================================================\n"); |
|
} |
|
|
|
/** |
|
* rsi_prepare_skb() - This function prepares the skb. |
|
* @common: Pointer to the driver private structure. |
|
* @buffer: Pointer to the packet data. |
|
* @pkt_len: Length of the packet. |
|
* @extended_desc: Extended descriptor. |
|
* |
|
* Return: Successfully skb. |
|
*/ |
|
static struct sk_buff *rsi_prepare_skb(struct rsi_common *common, |
|
u8 *buffer, |
|
u32 pkt_len, |
|
u8 extended_desc) |
|
{ |
|
struct sk_buff *skb = NULL; |
|
u8 payload_offset; |
|
|
|
if (WARN(!pkt_len, "%s: Dummy pkt received", __func__)) |
|
return NULL; |
|
|
|
if (pkt_len > (RSI_RCV_BUFFER_LEN * 4)) { |
|
rsi_dbg(ERR_ZONE, "%s: Pkt size > max rx buf size %d\n", |
|
__func__, pkt_len); |
|
pkt_len = RSI_RCV_BUFFER_LEN * 4; |
|
} |
|
|
|
pkt_len -= extended_desc; |
|
skb = dev_alloc_skb(pkt_len + FRAME_DESC_SZ); |
|
if (skb == NULL) |
|
return NULL; |
|
|
|
payload_offset = (extended_desc + FRAME_DESC_SZ); |
|
skb_put(skb, pkt_len); |
|
memcpy((skb->data), (buffer + payload_offset), skb->len); |
|
|
|
return skb; |
|
} |
|
|
|
/** |
|
* rsi_read_pkt() - This function reads frames from the card. |
|
* @common: Pointer to the driver private structure. |
|
* @rx_pkt: Received pkt. |
|
* @rcv_pkt_len: Received pkt length. In case of USB it is 0. |
|
* |
|
* Return: 0 on success, -1 on failure. |
|
*/ |
|
int rsi_read_pkt(struct rsi_common *common, u8 *rx_pkt, s32 rcv_pkt_len) |
|
{ |
|
u8 *frame_desc = NULL, extended_desc = 0; |
|
u32 index, length = 0, queueno = 0; |
|
u16 actual_length = 0, offset; |
|
struct sk_buff *skb = NULL; |
|
#ifdef CONFIG_RSI_COEX |
|
u8 bt_pkt_type; |
|
#endif |
|
|
|
index = 0; |
|
do { |
|
frame_desc = &rx_pkt[index]; |
|
actual_length = *(u16 *)&frame_desc[0]; |
|
offset = *(u16 *)&frame_desc[2]; |
|
|
|
queueno = rsi_get_queueno(frame_desc, offset); |
|
length = rsi_get_length(frame_desc, offset); |
|
|
|
/* Extended descriptor is valid for WLAN queues only */ |
|
if (queueno == RSI_WIFI_DATA_Q || queueno == RSI_WIFI_MGMT_Q) |
|
extended_desc = rsi_get_extended_desc(frame_desc, |
|
offset); |
|
|
|
switch (queueno) { |
|
case RSI_COEX_Q: |
|
#ifdef CONFIG_RSI_COEX |
|
if (common->coex_mode > 1) |
|
rsi_coex_recv_pkt(common, frame_desc + offset); |
|
else |
|
#endif |
|
rsi_mgmt_pkt_recv(common, |
|
(frame_desc + offset)); |
|
break; |
|
|
|
case RSI_WIFI_DATA_Q: |
|
skb = rsi_prepare_skb(common, |
|
(frame_desc + offset), |
|
length, |
|
extended_desc); |
|
if (skb == NULL) |
|
goto fail; |
|
|
|
rsi_indicate_pkt_to_os(common, skb); |
|
break; |
|
|
|
case RSI_WIFI_MGMT_Q: |
|
rsi_mgmt_pkt_recv(common, (frame_desc + offset)); |
|
break; |
|
|
|
#ifdef CONFIG_RSI_COEX |
|
case RSI_BT_MGMT_Q: |
|
case RSI_BT_DATA_Q: |
|
#define BT_RX_PKT_TYPE_OFST 14 |
|
#define BT_CARD_READY_IND 0x89 |
|
bt_pkt_type = frame_desc[offset + BT_RX_PKT_TYPE_OFST]; |
|
if (bt_pkt_type == BT_CARD_READY_IND) { |
|
rsi_dbg(INFO_ZONE, "BT Card ready recvd\n"); |
|
if (rsi_bt_ops.attach(common, &g_proto_ops)) |
|
rsi_dbg(ERR_ZONE, |
|
"Failed to attach BT module\n"); |
|
} else { |
|
if (common->bt_adapter) |
|
rsi_bt_ops.recv_pkt(common->bt_adapter, |
|
frame_desc + offset); |
|
} |
|
break; |
|
#endif |
|
|
|
default: |
|
rsi_dbg(ERR_ZONE, "%s: pkt from invalid queue: %d\n", |
|
__func__, queueno); |
|
goto fail; |
|
} |
|
|
|
index += actual_length; |
|
rcv_pkt_len -= actual_length; |
|
} while (rcv_pkt_len > 0); |
|
|
|
return 0; |
|
fail: |
|
return -EINVAL; |
|
} |
|
EXPORT_SYMBOL_GPL(rsi_read_pkt); |
|
|
|
/** |
|
* rsi_tx_scheduler_thread() - This function is a kernel thread to send the |
|
* packets to the device. |
|
* @common: Pointer to the driver private structure. |
|
* |
|
* Return: None. |
|
*/ |
|
static void rsi_tx_scheduler_thread(struct rsi_common *common) |
|
{ |
|
struct rsi_hw *adapter = common->priv; |
|
u32 timeout = EVENT_WAIT_FOREVER; |
|
|
|
do { |
|
if (adapter->determine_event_timeout) |
|
timeout = adapter->determine_event_timeout(adapter); |
|
rsi_wait_event(&common->tx_thread.event, timeout); |
|
rsi_reset_event(&common->tx_thread.event); |
|
|
|
if (common->init_done) |
|
rsi_core_qos_processor(common); |
|
} while (atomic_read(&common->tx_thread.thread_done) == 0); |
|
complete_and_exit(&common->tx_thread.completion, 0); |
|
} |
|
|
|
#ifdef CONFIG_RSI_COEX |
|
enum rsi_host_intf rsi_get_host_intf(void *priv) |
|
{ |
|
struct rsi_common *common = (struct rsi_common *)priv; |
|
|
|
return common->priv->rsi_host_intf; |
|
} |
|
|
|
void rsi_set_bt_context(void *priv, void *bt_context) |
|
{ |
|
struct rsi_common *common = (struct rsi_common *)priv; |
|
|
|
common->bt_adapter = bt_context; |
|
} |
|
#endif |
|
|
|
/** |
|
* rsi_91x_init() - This function initializes os interface operations. |
|
* @oper_mode: One of DEV_OPMODE_*. |
|
* |
|
* Return: Pointer to the adapter structure on success, NULL on failure . |
|
*/ |
|
struct rsi_hw *rsi_91x_init(u16 oper_mode) |
|
{ |
|
struct rsi_hw *adapter = NULL; |
|
struct rsi_common *common = NULL; |
|
u8 ii = 0; |
|
|
|
adapter = kzalloc(sizeof(*adapter), GFP_KERNEL); |
|
if (!adapter) |
|
return NULL; |
|
|
|
adapter->priv = kzalloc(sizeof(*common), GFP_KERNEL); |
|
if (adapter->priv == NULL) { |
|
rsi_dbg(ERR_ZONE, "%s: Failed in allocation of memory\n", |
|
__func__); |
|
kfree(adapter); |
|
return NULL; |
|
} else { |
|
common = adapter->priv; |
|
common->priv = adapter; |
|
} |
|
|
|
for (ii = 0; ii < NUM_SOFT_QUEUES; ii++) |
|
skb_queue_head_init(&common->tx_queue[ii]); |
|
|
|
rsi_init_event(&common->tx_thread.event); |
|
mutex_init(&common->mutex); |
|
mutex_init(&common->tx_lock); |
|
mutex_init(&common->rx_lock); |
|
mutex_init(&common->tx_bus_mutex); |
|
|
|
if (rsi_create_kthread(common, |
|
&common->tx_thread, |
|
rsi_tx_scheduler_thread, |
|
"Tx-Thread")) { |
|
rsi_dbg(ERR_ZONE, "%s: Unable to init tx thrd\n", __func__); |
|
goto err; |
|
} |
|
|
|
rsi_default_ps_params(adapter); |
|
init_bgscan_params(common); |
|
spin_lock_init(&adapter->ps_lock); |
|
timer_setup(&common->roc_timer, rsi_roc_timeout, 0); |
|
init_completion(&common->wlan_init_completion); |
|
adapter->device_model = RSI_DEV_9113; |
|
common->oper_mode = oper_mode; |
|
|
|
/* Determine coex mode */ |
|
switch (common->oper_mode) { |
|
case DEV_OPMODE_STA_BT_DUAL: |
|
case DEV_OPMODE_STA_BT: |
|
case DEV_OPMODE_STA_BT_LE: |
|
case DEV_OPMODE_BT_ALONE: |
|
case DEV_OPMODE_BT_LE_ALONE: |
|
case DEV_OPMODE_BT_DUAL: |
|
common->coex_mode = 2; |
|
break; |
|
case DEV_OPMODE_AP_BT_DUAL: |
|
case DEV_OPMODE_AP_BT: |
|
common->coex_mode = 4; |
|
break; |
|
case DEV_OPMODE_WIFI_ALONE: |
|
common->coex_mode = 1; |
|
break; |
|
default: |
|
common->oper_mode = 1; |
|
common->coex_mode = 1; |
|
} |
|
rsi_dbg(INFO_ZONE, "%s: oper_mode = %d, coex_mode = %d\n", |
|
__func__, common->oper_mode, common->coex_mode); |
|
|
|
adapter->device_model = RSI_DEV_9113; |
|
#ifdef CONFIG_RSI_COEX |
|
if (common->coex_mode > 1) { |
|
if (rsi_coex_attach(common)) { |
|
rsi_dbg(ERR_ZONE, "Failed to init coex module\n"); |
|
goto err; |
|
} |
|
} |
|
#endif |
|
|
|
common->init_done = true; |
|
return adapter; |
|
|
|
err: |
|
kfree(common); |
|
kfree(adapter); |
|
return NULL; |
|
} |
|
EXPORT_SYMBOL_GPL(rsi_91x_init); |
|
|
|
/** |
|
* rsi_91x_deinit() - This function de-intializes os intf operations. |
|
* @adapter: Pointer to the adapter structure. |
|
* |
|
* Return: None. |
|
*/ |
|
void rsi_91x_deinit(struct rsi_hw *adapter) |
|
{ |
|
struct rsi_common *common = adapter->priv; |
|
u8 ii; |
|
|
|
rsi_dbg(INFO_ZONE, "%s: Performing deinit os ops\n", __func__); |
|
|
|
rsi_kill_thread(&common->tx_thread); |
|
|
|
for (ii = 0; ii < NUM_SOFT_QUEUES; ii++) |
|
skb_queue_purge(&common->tx_queue[ii]); |
|
|
|
#ifdef CONFIG_RSI_COEX |
|
if (common->coex_mode > 1) { |
|
if (common->bt_adapter) { |
|
rsi_bt_ops.detach(common->bt_adapter); |
|
common->bt_adapter = NULL; |
|
} |
|
rsi_coex_detach(common); |
|
} |
|
#endif |
|
|
|
common->init_done = false; |
|
|
|
kfree(common); |
|
kfree(adapter->rsi_dev); |
|
kfree(adapter); |
|
} |
|
EXPORT_SYMBOL_GPL(rsi_91x_deinit); |
|
|
|
/** |
|
* rsi_91x_hal_module_init() - This function is invoked when the module is |
|
* loaded into the kernel. |
|
* It registers the client driver. |
|
* @void: Void. |
|
* |
|
* Return: 0 on success, -1 on failure. |
|
*/ |
|
static int rsi_91x_hal_module_init(void) |
|
{ |
|
rsi_dbg(INIT_ZONE, "%s: Module init called\n", __func__); |
|
return 0; |
|
} |
|
|
|
/** |
|
* rsi_91x_hal_module_exit() - This function is called at the time of |
|
* removing/unloading the module. |
|
* It unregisters the client driver. |
|
* @void: Void. |
|
* |
|
* Return: None. |
|
*/ |
|
static void rsi_91x_hal_module_exit(void) |
|
{ |
|
rsi_dbg(INIT_ZONE, "%s: Module exit called\n", __func__); |
|
} |
|
|
|
module_init(rsi_91x_hal_module_init); |
|
module_exit(rsi_91x_hal_module_exit); |
|
MODULE_AUTHOR("Redpine Signals Inc"); |
|
MODULE_DESCRIPTION("Station driver for RSI 91x devices"); |
|
MODULE_VERSION("0.1"); |
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
|