diff options
Diffstat (limited to 'mwl_main.c')
-rw-r--r-- | mwl_main.c | 1070 |
1 files changed, 1070 insertions, 0 deletions
diff --git a/mwl_main.c b/mwl_main.c new file mode 100644 index 0000000..c0b70df --- /dev/null +++ b/mwl_main.c @@ -0,0 +1,1070 @@ +/* +* Copyright (c) 2006-2014 Marvell International Ltd. +* +* 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. +*/ + +/* +* +* Description: This file implements main functions of this module. +* +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include "mwl_sysadpt.h" +#include "mwl_dev.h" +#include "mwl_debug.h" +#include "mwl_fwdl.h" +#include "mwl_fwcmd.h" +#include "mwl_tx.h" +#include "mwl_rx.h" +#include "mwl_mac80211.h" + +/* CONSTANTS AND MACROS +*/ + +#define MWL_DESC "Marvell 802.11ac Wireless Network Driver" +#define MWL_DEV_NAME "Marvell 88W8864 802.11ac Adapter" +#define MWL_DRV_NAME KBUILD_MODNAME +#define MWL_DRV_VERSION "10.2.6.1.p4" + +#define FILE_PATH_LEN 64 +#define CMD_BUF_SIZE 0x4000 + +#define INVALID_WATCHDOG 0xAA + +/* PRIVATE FUNCTION DECLARATION +*/ + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id); +static void mwl_remove(struct pci_dev *pdev); +static int mwl_alloc_pci_resource(struct mwl_priv *priv); +static void mwl_free_pci_resource(struct mwl_priv *priv); +static int mwl_init_firmware(struct mwl_priv *priv, char *fw_image); +static int mwl_load_tx_pwr_tbl(struct mwl_priv *priv, char *pwr_tbl); +static void mwl_set_ht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band); +static void mwl_set_vht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band); +static void mwl_set_caps(struct mwl_priv *priv); +static int mwl_wl_init(struct mwl_priv *priv); +static void mwl_wl_deinit(struct mwl_priv *priv); +static void mwl_watchdog_ba_events(struct work_struct *work); +static irqreturn_t mwl_interrupt(int irq, void *dev_id); + +static int atoi(const char *num_str); +static long atohex(const char *number); +static long atohex2(const char *number); + +/* PRIVATE VARIABLES +*/ + +static char fw_image_path[FILE_PATH_LEN] = "88W8864.bin"; +static char pwr_tbl_path[FILE_PATH_LEN] = "PWR_TABLE.ini"; + +static struct pci_device_id mwl_pci_id_tbl[SYSADPT_MAX_CARDS_SUPPORT + 1] = { + + { 0x11ab, 0x2a55, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)MWL_DEV_NAME }, + { 0, 0, 0, 0, 0, 0, 0 } + +}; + +static struct pci_driver mwl_pci_driver = { + + .name = MWL_DRV_NAME, + .id_table = mwl_pci_id_tbl, + .probe = mwl_probe, + .remove = mwl_remove + +}; + +static const struct ieee80211_channel mwl_channels_24[] = { + + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, + +}; + +static const struct ieee80211_rate mwl_rates_24[] = { + + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, + +}; + +static const struct ieee80211_channel mwl_channels_50[] = { + + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, }, + +}; + +static const struct ieee80211_rate mwl_rates_50[] = { + + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, + +}; + +static const struct ieee80211_iface_limit ap_if_limits[] = { + + { .max = 8, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, + +}; + +static const struct ieee80211_iface_combination ap_if_comb = { + + .limits = ap_if_limits, + .n_limits = ARRAY_SIZE(ap_if_limits), + .max_interfaces = 8, + .num_different_channels = 1, + +}; + +/* PUBLIC FUNCTION DEFINITION +*/ + +module_param_string(fw_name, fw_image_path, FILE_PATH_LEN, 0); +MODULE_PARM_DESC(fw_name, "Specify where to load the F/W image"); +module_param_string(pwr_tbl, pwr_tbl_path, FILE_PATH_LEN, 0); +MODULE_PARM_DESC(pwr_tbl, "Specify where to load TX power table"); + +module_pci_driver(mwl_pci_driver); + +MODULE_DESCRIPTION(MWL_DESC); +MODULE_VERSION(MWL_DRV_VERSION); +MODULE_AUTHOR("Marvell Semiconductor, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_SUPPORTED_DEVICE(MWL_DEV_NAME); +MODULE_DEVICE_TABLE(pci, mwl_pci_id_tbl); + +/* PRIVATE FUNCTION DEFINITION +*/ + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + static int printed_version; + struct ieee80211_hw *hw; + struct mwl_priv *priv; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + if (!printed_version) { + + WLDBG_PRINT("<<%s version %s>>", MWL_DESC, MWL_DRV_VERSION); + printed_version = 1; + } + + rc = pci_enable_device(pdev); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot enable new PCI device", + MWL_DRV_NAME); + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init error"); + return rc; + } + + rc = pci_set_dma_mask(pdev, 0xffffffff); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: 32-bit PCI DMA not supported", + MWL_DRV_NAME); + goto err_pci_disable_device; + } + + pci_set_master(pdev); + + hw = ieee80211_alloc_hw(sizeof(*priv), mwl_mac80211_get_ops()); + if (hw == NULL) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: ieee80211 alloc failed", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_pci_disable_device; + } + + /* set interrupt service routine to mac80211 module + */ + mwl_mac80211_set_isr(mwl_interrupt); + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + priv = hw->priv; + priv->hw = hw; + priv->pdev = pdev; + + rc = mwl_alloc_pci_resource(priv); + if (rc) + goto err_alloc_pci_resource; + + rc = mwl_init_firmware(priv, fw_image_path); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize firmware", + MWL_DRV_NAME); + goto err_init_firmware; + } + + /* firmware is loaded to H/W, it can be released now + */ + release_firmware(priv->fw_ucode); + + rc = mwl_load_tx_pwr_tbl(priv, pwr_tbl_path); + if (rc) { + + WLDBG_PRINT("%s: fail to load tx power table", + MWL_DRV_NAME); + } + + rc = mwl_wl_init(priv); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize wireless lan", + MWL_DRV_NAME); + goto err_wl_init; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_wl_init: +err_init_firmware: + + mwl_fwcmd_reset(hw); + +err_alloc_pci_resource: + + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + +err_pci_disable_device: + + pci_disable_device(pdev); + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init error"); + + return rc; +} + +static void mwl_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct mwl_priv *priv; + + WLDBG_ENTER(DBG_LEVEL_0); + + if (hw == NULL) { + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "ieee80211 hw is null"); + return; + } + + priv = hw->priv; + BUG_ON(!priv); + + mwl_wl_deinit(priv); + mwl_free_pci_resource(priv); + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + pci_disable_device(pdev); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_alloc_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + u32 phys_addr = 0; + u32 flags; + void *phys_addr1[2]; + void *phys_addr2[2]; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + phys_addr = pci_resource_start(pdev, 0); + flags = pci_resource_flags(pdev, 0); + + priv->next_bar_num = 1; /* 32-bit */ + + if (flags & 0x04) + priv->next_bar_num = 2; /* 64-bit */ + + if (!request_mem_region(phys_addr, pci_resource_len(pdev, 0), MWL_DRV_NAME)) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot reserve PCI memory region 0", + MWL_DRV_NAME); + goto err_reserve_mem_region_bar0; + } + + phys_addr1[0] = ioremap(phys_addr, pci_resource_len(pdev, 0)); + phys_addr1[1] = 0; + + priv->iobase0 = phys_addr1[0]; + if (!priv->iobase0) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot remap PCI memory region 0", + MWL_DRV_NAME); + goto err_release_mem_region_bar0; + } + + WLDBG_PRINT("priv->iobase0 = %x", (unsigned int)priv->iobase0); + + phys_addr = pci_resource_start(pdev, priv->next_bar_num); + + if (!request_mem_region(phys_addr, pci_resource_len(pdev, priv->next_bar_num), MWL_DRV_NAME)) { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot reserve PCI memory region 1", + MWL_DRV_NAME); + goto err_iounmap_iobase0; + } + + phys_addr2[0] = ioremap(phys_addr, pci_resource_len(pdev, priv->next_bar_num)); + phys_addr2[1] = 0; + priv->iobase1 = phys_addr2[0]; + + if (!priv->iobase1) + { + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot remap PCI memory region 1", + MWL_DRV_NAME); + goto err_release_mem_region_bar1; + } + + WLDBG_PRINT("priv->iobase1 = %x", (unsigned int)priv->iobase1); + + priv->pcmd_buf = (unsigned short *) + pci_alloc_consistent(priv->pdev, CMD_BUF_SIZE, &priv->pphys_cmd_buf); + + if (priv->pcmd_buf == NULL) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot alloc memory for command buffer", + MWL_DRV_NAME); + goto err_iounmap_iobase1; + } + + WLDBG_PRINT("priv->pcmd_buf = %x priv->pphys_cmd_buf = %x", + (unsigned int)priv->pcmd_buf, (unsigned int)priv->pphys_cmd_buf); + + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + + WLDBG_EXIT(DBG_LEVEL_0); + + return 0; + +err_iounmap_iobase1: + + iounmap(priv->iobase1); + +err_release_mem_region_bar1: + + release_mem_region(pci_resource_start(pdev, 1), pci_resource_len(pdev, 1)); + +err_iounmap_iobase0: + + iounmap(priv->iobase0); + +err_release_mem_region_bar0: + + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + +err_reserve_mem_region_bar0: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "pci alloc fail"); + + return -EIO; + +} + +static void mwl_free_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + iounmap(priv->iobase0); + iounmap(priv->iobase1); + release_mem_region(pci_resource_start(pdev, priv->next_bar_num), pci_resource_len(pdev, priv->next_bar_num)); + release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + pci_free_consistent(priv->pdev, CMD_BUF_SIZE, priv->pcmd_buf, priv->pphys_cmd_buf); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_init_firmware(struct mwl_priv *priv, char *fw_name) +{ + struct pci_dev *pdev; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + pdev = priv->pdev; + BUG_ON(!pdev); + + rc = request_firmware(&priv->fw_ucode, fw_name, &priv->pdev->dev); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot load firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_load_fw; + } + + rc = mwl_fwdl_download_firmware(priv->hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: cannot download firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_download_fw; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_download_fw: + + release_firmware(priv->fw_ucode); + +err_load_fw: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "firmware init fail"); + + return rc; +} + +static int mwl_load_tx_pwr_tbl(struct mwl_priv *priv, char *pwr_tbl) +{ + struct file *filp = NULL; + mm_segment_t oldfs; + char buff[120], *s; + int len, index = 0, i, value = 0; + char param[20][32]; + int rc = 0; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + + oldfs = get_fs(); + set_fs(KERNEL_DS); + + filp = filp_open(pwr_tbl, O_RDONLY, 0); + + if (!IS_ERR(filp)) { /* MUST use this one, important!!! */ + + WLDBG_PRINT("open tx power table <%s>: OK", pwr_tbl); + + /* reset the whole table */ + for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) + memset(&priv->tx_pwr_tbl[i], 0, sizeof(struct mwl_tx_pwr_tbl)); + + while (1) { + + s = buff; + while ((len = vfs_read(filp, s, 0x01, &filp->f_pos)) == 1) { + + if (*s == '\n') { + /* skip blank line */ + if (s == buff) + break; + + /* parse this line and assign value to data structure */ + *s = '\0'; + /* WLDBG_PRINT("index=<%d>: <%s>", index, buff); */ + /* 8864 total param: ch + setcap + 16 txpower + CDD + tx2 = 16 */ + sscanf(buff, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n", param[0], param[1], param[2], param[3], param[4], param[5] + , param[6], param[7], param[8], param[9], param[10], param[11], param[12], param[13], param[14], param[15], param[16], param[17] + , param[18], param[19]); + + if (strcmp(param[18], "on") == 0) + value = 1; + else if (strcmp(param[18], "off") == 0) + value = 0; + else { + WLDBG_PRINT("txpower table format error: CCD should be on|off"); + break; + } + + priv->tx_pwr_tbl[index].cdd = value; + priv->tx_pwr_tbl[index].txantenna2 = atohex2(param[19]); + priv->tx_pwr_tbl[index].channel = atoi(param[0]); + priv->tx_pwr_tbl[index].setcap = atoi(param[1]); + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + priv->tx_pwr_tbl[index].tx_power[i] = atohex2(param[i+2]); + + index++; + break; + + } else { + + s++; + + } + } + + if (len <= 0) + break; + } + + filp_close(filp, current->files); + + } else { + + WLDBG_PRINT("open tx power table <%s>: FAIL", pwr_tbl); + rc = -EIO; + + } + + set_fs(oldfs); + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "result: %d", rc); + + return rc; +} + +static void mwl_set_ht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band) +{ + band->ht_cap.ht_supported = 1; + + band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + + hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + band->ht_cap.mcs.rx_mask[0] = 0xff; + band->ht_cap.mcs.rx_mask[1] = 0xff; + band->ht_cap.mcs.rx_mask[2] = 0xff; + band->ht_cap.mcs.rx_mask[4] = 0x01; + + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void mwl_set_vht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band) +{ + band->vht_cap.vht_supported = 1; + + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + band->vht_cap.vht_mcs.rx_mcs_map = 0xffea; + band->vht_cap.vht_mcs.tx_mcs_map = 0xffea; +} + +static void mwl_set_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + /* set up band information for 2.4G + */ + BUILD_BUG_ON(sizeof(priv->channels_24) != sizeof(mwl_channels_24)); + memcpy(priv->channels_24, mwl_channels_24, sizeof(mwl_channels_24)); + + BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24)); + memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24)); + + priv->band_24.band = IEEE80211_BAND_2GHZ; + priv->band_24.channels = priv->channels_24; + priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24); + priv->band_24.bitrates = priv->rates_24; + priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24); + + mwl_set_ht_caps(hw, &priv->band_24); + mwl_set_vht_caps(hw, &priv->band_24); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band_24; + + /* set up band information for 5G + */ + BUILD_BUG_ON(sizeof(priv->channels_50) != sizeof(mwl_channels_50)); + memcpy(priv->channels_50, mwl_channels_50, sizeof(mwl_channels_50)); + + BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50)); + memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50)); + + priv->band_50.band = IEEE80211_BAND_5GHZ; + priv->band_50.channels = priv->channels_50; + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + priv->band_50.bitrates = priv->rates_50; + priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50); + + mwl_set_ht_caps(hw, &priv->band_50); + mwl_set_vht_caps(hw, &priv->band_50); + + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &priv->band_50; + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static int mwl_wl_init(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + int rc; + int i; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + /* + * Extra headroom is the size of the required DMA header + * minus the size of the smallest 802.11 frame (CTS frame). + */ + hw->extra_tx_headroom = + sizeof(struct mwl_dma_data) - sizeof(struct ieee80211_cts); + hw->queues = SYSADPT_TX_WMM_QUEUES; + + /* Set rssi values to dBm + */ + hw->flags |= IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_HAS_RATE_CONTROL; + + /* + * Ask mac80211 to not to trigger PS mode + * based on PM bit of incoming frames. + */ + hw->flags |= IEEE80211_HW_AP_LINK_PS; + + hw->vif_data_size = sizeof(struct mwl_vif); + hw->sta_data_size = sizeof(struct mwl_sta); + + priv->ap_macids_supported = 0x000000ff; + priv->sta_macids_supported = 0x00000100; + priv->macids_used = 0; + INIT_LIST_HEAD(&priv->vif_list); + + /* Set default radio state, preamble and wmm + */ + priv->radio_on = false; + priv->radio_short_preamble = false; + priv->wmm_enabled = false; + + priv->powinited = 0; + + /* Handle watchdog ba events + */ + INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events); + + tasklet_init(&priv->tx_task, (void *)mwl_tx_done, (unsigned long)hw); + tasklet_disable(&priv->tx_task); + tasklet_init(&priv->rx_task, (void *)mwl_rx_recv, (unsigned long)hw); + tasklet_disable(&priv->rx_task); + priv->txq_limit = SYSADPT_TX_QUEUE_LIMIT; + priv->is_tx_schedule = false; + priv->recv_limit = SYSADPT_RECEIVE_LIMIT; + priv->is_rx_schedule = false; + + SPIN_LOCK_INIT(&priv->locks.xmit_lock); + SPIN_LOCK_INIT(&priv->locks.fwcmd_lock); + SPIN_LOCK_INIT(&priv->locks.stream_lock); + + rc = mwl_tx_init(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize TX", + MWL_DRV_NAME); + goto err_mwl_tx_init; + } + + rc = mwl_rx_init(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to initialize RX", + MWL_DRV_NAME); + goto err_mwl_rx_init; + } + + rc = mwl_fwcmd_get_hw_specs(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to get HW specifications", + MWL_DRV_NAME); + goto err_get_hw_specs; + } + + SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr); + + writel(priv->desc_data[0].pphys_tx_ring, + priv->iobase0 + priv->desc_data[0].wcb_base); +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + writel(priv->desc_data[i].pphys_tx_ring, + priv->iobase0 + priv->desc_data[i].wcb_base); +#endif + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_read); + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_write); + + rc = mwl_fwcmd_set_hw_specs(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to set HW specifications", + MWL_DRV_NAME); + goto err_set_hw_specs; + } + + mwl_fwcmd_radio_disable(hw); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_RX, 0); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_TX, 0); + + hw->wiphy->interface_modes = 0; + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + hw->wiphy->iface_combinations = &ap_if_comb; + hw->wiphy->n_iface_combinations = 1; + + mwl_set_caps(priv); + + rc = ieee80211_register_hw(hw); + if (rc) { + + WLDBG_ERROR(DBG_LEVEL_0, "%s: fail to register device", + MWL_DRV_NAME); + goto err_register_hw; + } + + WLDBG_EXIT(DBG_LEVEL_0); + + return rc; + +err_register_hw: +err_set_hw_specs: +err_get_hw_specs: + + mwl_rx_deinit(hw); + +err_mwl_rx_init: + + mwl_tx_deinit(hw); + +err_mwl_tx_init: + + WLDBG_EXIT_INFO(DBG_LEVEL_0, "init fail"); + + return rc; +} + +static void mwl_wl_deinit(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + WLDBG_ENTER(DBG_LEVEL_0); + + BUG_ON(!priv); + hw = priv->hw; + BUG_ON(!hw); + + ieee80211_unregister_hw(hw); + mwl_rx_deinit(hw); + mwl_tx_deinit(hw); + tasklet_kill(&priv->rx_task); + tasklet_kill(&priv->tx_task); + mwl_fwcmd_reset(hw); + + WLDBG_EXIT(DBG_LEVEL_0); +} + +static void mwl_watchdog_ba_events(struct work_struct *work) +{ + int rc; + u8 bitmap = 0, stream_index; + struct mwl_ampdu_stream *streams; + struct mwl_priv *priv = + container_of(work, struct mwl_priv, watchdog_ba_handle); + struct ieee80211_hw *hw = priv->hw; + u32 status; + + rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap); + + if (rc) + goto done; + + SPIN_LOCK(&priv->locks.stream_lock); + + /* the bitmap is the hw queue number. Map it to the ampdu queue. + */ + if (bitmap != INVALID_WATCHDOG) { + + if (bitmap == SYSADPT_TX_AMPDU_QUEUES) + stream_index = 0; + else if (bitmap > SYSADPT_TX_AMPDU_QUEUES) + stream_index = bitmap - SYSADPT_TX_AMPDU_QUEUES; + else + stream_index = bitmap + 3; /** queue 0 is stream 3*/ + + if (bitmap != 0xFF) { + + /* Check if the stream is in use before disabling it + */ + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) { + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + SPIN_LOCK(&priv->locks.stream_lock); + } + + } else { + + for (stream_index = 0; stream_index < SYSADPT_TX_AMPDU_QUEUES; stream_index++) { + + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) { + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + SPIN_UNLOCK(&priv->locks.stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + SPIN_LOCK(&priv->locks.stream_lock); + } + } + } + } + + SPIN_UNLOCK(&priv->locks.stream_lock); + +done: + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status | MACREG_A2HRIC_BA_WATCHDOG, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + return; +} + +static irqreturn_t mwl_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct mwl_priv *priv; + unsigned int int_status, clr_status; + u32 status; + + BUG_ON(!hw); + priv = hw->priv; + BUG_ON(!priv); + + int_status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + + if (int_status == 0x00000000) + return IRQ_NONE; + + if (int_status == 0xffffffff) { + + WLDBG_INFO(DBG_LEVEL_0, "card plugged out???"); + + } else { + + clr_status = int_status; + + if (int_status & MACREG_A2HRIC_BIT_TX_DONE) { + + int_status &= ~MACREG_A2HRIC_BIT_TX_DONE; + + if (priv->is_tx_schedule == false) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BIT_TX_DONE), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(&priv->tx_task); + priv->is_tx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BIT_RX_RDY) { + + int_status &= ~MACREG_A2HRIC_BIT_RX_RDY; + + if (priv->is_rx_schedule == false) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BIT_RX_RDY), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(&priv->rx_task); + priv->is_rx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BA_WATCHDOG) { + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel((status & ~MACREG_A2HRIC_BA_WATCHDOG), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + int_status &= ~MACREG_A2HRIC_BA_WATCHDOG; + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + } + + writel(~clr_status, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + } + + return IRQ_HANDLED; +} + +static int atoi(const char *num_str) +{ + int val = 0; + + for (;; num_str++) { + + switch (*num_str) { + + case '0'...'9': + val = 10*val+(*num_str-'0'); + break; + + default: + return val; + } + } +} + +static long atohex(const char *number) +{ + long n = 0; + + if (*number == '0' && (*(number + 1) == 'x' || *(number + 1) == 'X')) + number += 2; + + while (*number <= ' ' && *number > 0) + ++number; + + while ((*number >= '0' && *number <= '9') || + (*number >= 'A' && *number <= 'F') || + (*number >= 'a' && *number <= 'f')) { + + if (*number >= '0' && *number <= '9') { + + n = (n * 0x10) + ((*number++) - '0'); + + } else if (*number >= 'A' && *number <= 'F') { + + n = (n * 0x10) + ((*number++) - 'A' + 10); + + } else { + + n = (n * 0x10) + ((*number++) - 'a' + 10); + + } + } + + return n; +} + +static long atohex2(const char *number) +{ + long n = 0; + + while (*number <= ' ' && *number > 0) + ++number; + + if (*number == 0) + return n; + + if (*number == '0' && (*(number + 1) == 'x' || *(number + 1) == 'X')) + n = atohex(number+2); + else + n = atoi(number); + + return n; +} |