/*
* 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;
}
