/** @file mlan_usb.c
 *
 *  @brief This file contains USB specific code
 *
 *
 *  Copyright 2008-2021 NXP
 *
 *  This software file (the File) is distributed by NXP
 *  under the terms of the GNU General Public License Version 2, June 1991
 *  (the License).  You may use, redistribute and/or modify the File in
 *  accordance with the terms and conditions of the License, a copy of which
 *  is available by writing to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
 *  worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.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.
 *
 */

/********************************************************
Change log:
    04/21/2009: initial version
********************************************************/

#include "mlan.h"
#ifdef STA_SUPPORT
#include "mlan_join.h"
#endif
#include "mlan_util.h"
#include "mlan_init.h"
#include "mlan_fw.h"
#include "mlan_main.h"

/********************************************************
			Local Variables
********************************************************/
#ifdef USB8801
static const struct _mlan_card_info mlan_card_info_usb8801 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_2K,
	.v14_fw_api = 1,
	.v16_fw_api = 0,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_1X1,
};
#endif
#ifdef USB8897
static const struct _mlan_card_info mlan_card_info_usb8897 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K,
	.v16_fw_api = 0,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2,
};
#endif

#ifdef USB8997
static const struct _mlan_card_info mlan_card_info_usb8997 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K,
	.v16_fw_api = 1,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2,
};
#endif

#ifdef USB8978
static const struct _mlan_card_info mlan_card_info_usb8978 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K,
	.v16_fw_api = 1,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2,
};
#endif

#ifdef USB9098
static const struct _mlan_card_info mlan_card_info_usb9098 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K,
	.v16_fw_api = 1,
	.v17_fw_api = 1,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2,
};
#endif

#ifdef USB9097
static const struct _mlan_card_info mlan_card_info_usb9097 = {
	.max_tx_buf_size = MLAN_TX_DATA_BUF_SIZE_4K,
	.v16_fw_api = 1,
	.v17_fw_api = 1,
	.supp_ps_handshake = 1,
	.default_11n_tx_bf_cap = DEFAULT_11N_TX_BF_CAP_2X2,
};
#endif

/********************************************************
			Global Variables
********************************************************/

/********************************************************
			Local Functions
********************************************************/
#if defined(USB9098)
/**
 *  @This function checks the chip revision id
 *
 *  @param pmadapter  A pointer to mlan_adapter structure
 *  @param rev_id         A pointer to chip revision id
 *  @return        MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status wlan_usb_check_revision(mlan_adapter *pmadapter,
					   t_u32 *rev_id)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	pmlan_callbacks pcb = &pmadapter->callbacks;
	mlan_buffer mbuf;
	t_u8 *tx_buff = MNULL;
	t_u8 *recv_buff = MNULL;
	t_u8 tx_size = 16;
	FWSyncPkt syncpkt;

	ENTER();
	/* Allocate memory for transmit */
	ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_TX_BUF_SIZE,
			       MLAN_MEM_DEF | MLAN_MEM_DMA, (t_u8 **)&tx_buff);
	if ((ret != MLAN_STATUS_SUCCESS) || !tx_buff) {
		PRINTM(MERROR, "Could not allocate buffer for FW download\n");
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}
	/* Allocate memory for receive */
	ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_RX_BUF_SIZE,
			       MLAN_MEM_DEF | MLAN_MEM_DMA, &recv_buff);
	if ((ret != MLAN_STATUS_SUCCESS) || !recv_buff) {
		PRINTM(MERROR,
		       "Could not allocate buffer for FW download response\n");
		goto cleanup;
	}
	memset(pmadapter, &syncpkt, 0, sizeof(FWSyncPkt));
	memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer));
	mbuf.pbuf = (t_u8 *)tx_buff;
	mbuf.data_len = tx_size;
	ret = pcb->moal_write_data_sync(pmadapter->pmoal_handle, &mbuf,
					pmadapter->tx_cmd_ep,
					MLAN_USB_BULK_MSG_TIMEOUT);
	if (ret != MLAN_STATUS_SUCCESS) {
		PRINTM(MERROR, "check revision: write_data failed, ret %d\n",
		       ret);
		goto cleanup;
	}
	memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer));
	mbuf.pbuf = (t_u8 *)recv_buff;
	mbuf.data_len = 2048;
	ret = pcb->moal_read_data_sync(pmadapter->pmoal_handle, &mbuf,
				       pmadapter->rx_cmd_ep,
				       MLAN_USB_BULK_MSG_TIMEOUT);
	if (ret != MLAN_STATUS_SUCCESS) {
		PRINTM(MERROR, "check revision: read_data failed, ret %d\n",
		       ret);
		goto cleanup;
	}
	memcpy_ext(pmadapter, &syncpkt, recv_buff, sizeof(syncpkt),
		   sizeof(syncpkt));
	syncpkt.chip_rev = wlan_le32_to_cpu(syncpkt.chip_rev);
	*rev_id = syncpkt.chip_rev & 0x000000ff;
	PRINTM(MERROR, "chip_revision_id = %d\n", syncpkt.chip_rev);
cleanup:
	if (recv_buff)
		pcb->moal_mfree(pmadapter->pmoal_handle, recv_buff);
	if (tx_buff)
		pcb->moal_mfree(pmadapter->pmoal_handle, tx_buff);

	LEAVE();
	return ret;
}
#endif

/**
 *  @brief  This function downloads FW blocks to device
 *
 *  @param pmadapter	A pointer to mlan_adapter
 *  @param pmfw			A pointer to firmware image
 *
 *  @return             MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status wlan_usb_prog_fw_w_helper(pmlan_adapter pmadapter,
					     pmlan_fw_image pmfw)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	pmlan_callbacks pcb = &pmadapter->callbacks;
	t_u8 *firmware = pmfw->pfw_buf, *RecvBuff;
	t_u32 retries = MAX_FW_RETRY, DataLength;
	t_u32 FWSeqNum = 0, TotalBytes = 0, DnldCmd = 0;
	t_u8 *TxBuff = MNULL;
	FWData *fwdata = MNULL;
	FWSyncHeader SyncFWHeader;
	t_u8 check_winner = 1;
	t_u8 check_fw_status = MFALSE;
	t_u8 mic_retry = MAX_FW_RETRY;
#if defined(USB9098)
	t_u32 revision_id = 0;
#endif

	ENTER();

	if (!firmware && !pcb->moal_get_fw_data) {
		PRINTM(MMSG, "No firmware image found! Terminating download\n");
		ret = MLAN_STATUS_FAILURE;
		goto fw_exit;
	}

	/* Allocate memory for transmit */
	ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_TX_BUF_SIZE,
			       MLAN_MEM_DEF | MLAN_MEM_DMA, (t_u8 **)&TxBuff);
	if ((ret != MLAN_STATUS_SUCCESS) || !TxBuff) {
		PRINTM(MERROR, "Could not allocate buffer for FW download\n");
		goto fw_exit;
	}
	fwdata = (FWData *)TxBuff;

	/* Allocate memory for receive */
	ret = pcb->moal_malloc(pmadapter->pmoal_handle, FW_DNLD_RX_BUF_SIZE,
			       MLAN_MEM_DEF | MLAN_MEM_DMA, &RecvBuff);
	if ((ret != MLAN_STATUS_SUCCESS) || !RecvBuff) {
		PRINTM(MERROR,
		       "Could not allocate buffer for FW download response\n");
		goto cleanup;
	}

	if (!IS_USB_NEW_INIT(pmadapter->feature_control))
		check_winner = 0;
#if defined(USB9098)
	if (IS_USB9098(pmadapter->card_type)) {
		ret = wlan_usb_check_revision(pmadapter, &revision_id);
		if (ret != MLAN_STATUS_SUCCESS) {
			PRINTM(MERROR, "Failed to get USB chip revision ID\n");
			goto cleanup;
		}
		/* Skyhawk A0, need to check both CRC and MIC error */
		if (revision_id >= CHIP_9098_REV_A0)
			check_fw_status = MTRUE;
	}
#endif
#if defined(USB9097)
	if (IS_USB9097(pmadapter->card_type))
		check_fw_status = MTRUE;
#endif
	do {
		/* Send pseudo data to check winner status first */
		if (check_winner) {
			memset(pmadapter, &fwdata->fw_header, 0,
			       sizeof(FWHeader));
			DataLength = 0;
		} else {
			/* Copy the header of the firmware data to get the
			 * length */
			if (firmware)
				memcpy_ext(pmadapter, &fwdata->fw_header,
					   &firmware[TotalBytes],
					   sizeof(FWHeader),
					   sizeof(fwdata->fw_header));
			else
				pcb->moal_get_fw_data(
					pmadapter->pmoal_handle, TotalBytes,
					sizeof(FWHeader),
					(t_u8 *)&fwdata->fw_header);

			DataLength =
				wlan_le32_to_cpu(fwdata->fw_header.data_length);
			DnldCmd = wlan_le32_to_cpu(fwdata->fw_header.dnld_cmd);
			TotalBytes += sizeof(FWHeader);

			/** CMD 7 don't have data_length field */
			if (DnldCmd == FW_CMD_4 || DnldCmd == FW_CMD_6 ||
			    DnldCmd == FW_CMD_7 || DnldCmd == FW_CMD_10)
				DataLength = 0;

			if (DataLength >
			    (FW_DNLD_TX_BUF_SIZE - sizeof(FWHeader))) {
				PRINTM(MERROR,
				       "Invalid Data Legth read from FW\n");
				ret = MLAN_STATUS_FAILURE;
				break;
			}

			/* Copy the firmware data */
			if (firmware)
				memcpy_ext(pmadapter, fwdata->data,
					   &firmware[TotalBytes], DataLength,
					   DataLength);
			else
				pcb->moal_get_fw_data(pmadapter->pmoal_handle,
						      TotalBytes, DataLength,
						      (t_u8 *)fwdata->data);

			fwdata->seq_num = wlan_cpu_to_le32(FWSeqNum);
			TotalBytes += DataLength;
		}
		/* If the send/receive fails or CRC occurs then retry */
		while (retries) {
			mlan_buffer mbuf;
			int length = FW_DATA_XMIT_SIZE;
			retries--;

			memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer));
			mbuf.pbuf = (t_u8 *)fwdata;
			mbuf.data_len = length;
			/* Send the firmware block */
			ret = pcb->moal_write_data_sync(
				pmadapter->pmoal_handle, &mbuf,
				pmadapter->tx_cmd_ep,
				MLAN_USB_BULK_MSG_TIMEOUT);
			if (ret != MLAN_STATUS_SUCCESS) {
				PRINTM(MERROR,
				       "fw_dnld: write_data failed, ret %d\n",
				       ret);
				continue;
			}

			memset(pmadapter, &mbuf, 0, sizeof(mlan_buffer));
			mbuf.pbuf = RecvBuff;
			mbuf.data_len = FW_DNLD_RX_BUF_SIZE;

			/* Receive the firmware block response */
			ret = pcb->moal_read_data_sync(
				pmadapter->pmoal_handle, &mbuf,
				pmadapter->rx_cmd_ep,
				MLAN_USB_BULK_MSG_TIMEOUT);
			if (ret != MLAN_STATUS_SUCCESS) {
				PRINTM(MERROR,
				       "fw_dnld: read_data failed, ret %d\n",
				       ret);
				continue;
			}
			memcpy_ext(pmadapter, &SyncFWHeader, RecvBuff,
				   sizeof(FWSyncHeader), sizeof(SyncFWHeader));
			endian_convert_syncfwheader(&SyncFWHeader);
			/* Check the first firmware block response for highest
			 * bit set */
			if (check_winner) {
				if (SyncFWHeader.cmd & 0x80000000) {
					PRINTM(MMSG,
					       "USB is not the winner 0x%x, returning success\n",
					       SyncFWHeader.cmd);
					ret = MLAN_STATUS_SUCCESS;
					goto cleanup;
				}
				PRINTM(MINFO,
				       "USB is the winner, start to download FW\n");
				check_winner = 0;
				break;
			}

			/* Check the firmware block response for CRC errors */
			if (SyncFWHeader.cmd) {
				/* Check firmware block response for CRC and MIC
				 * errors */
				if (check_fw_status) {
					if (SyncFWHeader.status & MBIT(0)) {
						PRINTM(MERROR,
						       "FW received Blk with CRC error 0x%x offset=%d\n",
						       SyncFWHeader.status,
						       SyncFWHeader.offset);
						ret = MLAN_STATUS_FAILURE;
						continue;
					}
					if (SyncFWHeader.status &
					    (MBIT(6) | MBIT(7))) {
						PRINTM(MERROR,
						       "FW received Blk with MIC error 0x%x offset\n",
						       SyncFWHeader.status,
						       SyncFWHeader.offset);
						mic_retry--;
						FWSeqNum = 0;
						TotalBytes = 0;
						ret = MLAN_STATUS_FAILURE;
						continue;
					}
				} else {
					PRINTM(MERROR,
					       "FW received Blk with CRC error 0x%x\n",
					       SyncFWHeader.cmd);
					ret = MLAN_STATUS_FAILURE;
					continue;
				}
			}

			retries = MAX_FW_RETRY;
			break;
		}

		FWSeqNum++;
		PRINTM(MINFO, ".\n");

	} while ((DnldCmd != FW_HAS_LAST_BLOCK) && retries && mic_retry);

cleanup:
	PRINTM(MMSG, "fw_dnld: %d bytes downloaded\n", TotalBytes);

	if (RecvBuff)
		pcb->moal_mfree(pmadapter->pmoal_handle, RecvBuff);
	if (TxBuff)
		pcb->moal_mfree(pmadapter->pmoal_handle, TxBuff);
	if (retries && mic_retry) {
		ret = MLAN_STATUS_SUCCESS;
	}

fw_exit:
	LEAVE();
	return ret;
}

/**
 *  @brief Get number of packets when deaggregated
 *
 *  @param pmadapter		A pointer to mlan_adapter
 *  @param pdata			A pointer to packet data
 *  @param aggr_pkt_len		Aggregate packet length
 *
 *  @return			Number of packets
 */
static int wlan_usb_deaggr_rx_num_pkts(pmlan_adapter pmadapter, t_u8 *pdata,
				       int aggr_pkt_len)
{
	int pkt_count = 0, pkt_len;
	RxPD *prx_pd;

	ENTER();
	while (aggr_pkt_len >= (int)sizeof(RxPD)) {
		prx_pd = (RxPD *)pdata;
		pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) +
			  wlan_le16_to_cpu(prx_pd->rx_pkt_offset);
		if (pkt_len == 0) /* blank RxPD can be at the end */
			break;

		++pkt_count;
		if (aggr_pkt_len == pkt_len) /* last packet has no padding */
			break;

		/* skip padding and goto next */
		if (pkt_len %
		    pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_align)
			pkt_len +=
				(pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl
					 .aggr_align -
				 (pkt_len % pmadapter->pcard_usb->usb_rx_deaggr
						    .aggr_ctrl.aggr_align));
		aggr_pkt_len -= pkt_len;
		pdata += pkt_len;
	}
	LEAVE();
	return pkt_count;
}

static inline t_u32 usb_tx_aggr_pad_len(t_u32 len,
					usb_tx_aggr_params *pusb_tx_aggr)
{
	return (len % pusb_tx_aggr->aggr_ctrl.aggr_align) ?
		       (len + (pusb_tx_aggr->aggr_ctrl.aggr_align -
			       (len % pusb_tx_aggr->aggr_ctrl.aggr_align))) :
		       len;
}

/**
 *  @brief Copy pmbuf to aggregation buffer
 *
 *  @param pmadapter	Pointer to mlan_adapter structure
 *  @param pmbuf_aggr	Pointer to aggregation buffer
 *  @param pmbuf		Pointer to buffer to copy
 *  @param pusb_tx_aggr Pointer to usb_tx_aggr_params
 *
 *  @return   N/A
 */
static inline t_void
wlan_usb_tx_copy_buf_to_aggr(pmlan_adapter pmadapter, pmlan_buffer pmbuf_aggr,
			     pmlan_buffer pmbuf,
			     usb_tx_aggr_params *pusb_tx_aggr)
{
	ENTER();
	pmbuf_aggr->data_len =
		usb_tx_aggr_pad_len(pmbuf_aggr->data_len, pusb_tx_aggr);
	memcpy_ext(pmadapter,
		   pmbuf_aggr->pbuf + pmbuf_aggr->data_offset +
			   pmbuf_aggr->data_len,
		   pmbuf->pbuf + pmbuf->data_offset, pmbuf->data_len,
		   pmbuf->data_len);
	pmbuf_aggr->data_len += pmbuf->data_len;
	LEAVE();
}

#define MLAN_TYPE_AGGR_DATA_V2 11
/**
 *  @brief Copy pmbuf to aggregation buffer
 *
 *  @param pmadapter	Pointer to mlan_adapter structure
 *  @param pmbuf_aggr	Pointer to aggregation buffer
 *  @param pmbuf		Pointer to buffer to copy
 *	@param last 		last packet flag
 *  @param pusb_tx_aggr Pointer to usb_tx_aggr_params
 *
 *  @return   N/A
 */
static inline t_void
wlan_usb_tx_copy_buf_to_aggr_v2(pmlan_adapter pmadapter,
				pmlan_buffer pmbuf_aggr, pmlan_buffer pmbuf,
				t_u8 last, usb_tx_aggr_params *pusb_tx_aggr)
{
	t_u8 *payload;
	t_u16 offset;

	ENTER();
	pmbuf_aggr->data_len =
		usb_tx_aggr_pad_len(pmbuf_aggr->data_len, pusb_tx_aggr);
	memcpy_ext(pmadapter,
		   pmbuf_aggr->pbuf + pmbuf_aggr->data_offset +
			   pmbuf_aggr->data_len,
		   pmbuf->pbuf + pmbuf->data_offset, pmbuf->data_len,
		   pmbuf->data_len);
	payload = pmbuf_aggr->pbuf + pmbuf_aggr->data_offset +
		  pmbuf_aggr->data_len;
	if (last) {
		offset = pmbuf->data_len;
		*(t_u16 *)&payload[2] =
			wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2 | 0x80);
	} else {
		offset = usb_tx_aggr_pad_len(pmbuf->data_len, pusb_tx_aggr);
		*(t_u16 *)&payload[2] =
			wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2);
	}
	*(t_u16 *)&payload[0] = wlan_cpu_to_le16(offset);
	pmbuf_aggr->data_len += pmbuf->data_len;
	PRINTM(MIF_D, "offset=%d len=%d\n", offset, pmbuf->data_len);
	LEAVE();
}

/**
 *  @brief Allocate Aggregation buffer and copy pending buffers to it.
 *
 *  @param pmadapter	Pointer to mlan_adapter structure
 *  @param pusb_tx_aggr Pointer to usb_tx_aggr_params
 *
 *  @return			Aggregation buffer
 */
static inline pmlan_buffer
wlan_usb_copy_buf_to_aggr(pmlan_adapter pmadapter,
			  usb_tx_aggr_params *pusb_tx_aggr)
{
	pmlan_buffer pmbuf_aggr = MNULL;
	t_u8 i, use_count;
	pmlan_buffer pmbuf_curr, pmbuf_next;
	pmbuf_aggr = wlan_alloc_mlan_buffer(pmadapter, pusb_tx_aggr->aggr_len,
					    0, MOAL_MALLOC_BUFFER);
	if (pmbuf_aggr) {
		pmbuf_curr = pusb_tx_aggr->pmbuf_aggr;
		pmbuf_aggr->bss_index = pmbuf_curr->bss_index;
		pmbuf_aggr->buf_type = pmbuf_curr->buf_type;
		pmbuf_aggr->priority = pmbuf_curr->priority;
		pmbuf_aggr->data_len = 0;
		PRINTM(MIF_D, "use_count=%d,aggr_len=%d\n",
		       pmbuf_curr->use_count, pusb_tx_aggr->aggr_len);
		use_count = pmbuf_curr->use_count;
		for (i = 0; i <= use_count; i++) {
			pmbuf_next = pmbuf_curr->pnext;
			if (pusb_tx_aggr->aggr_ctrl.aggr_mode ==
			    MLAN_USB_AGGR_MODE_LEN_V2) {
				if (i == use_count)
					wlan_usb_tx_copy_buf_to_aggr_v2(
						pmadapter, pmbuf_aggr,
						pmbuf_curr, MTRUE,
						pusb_tx_aggr);
				else
					wlan_usb_tx_copy_buf_to_aggr_v2(
						pmadapter, pmbuf_aggr,
						pmbuf_curr, MFALSE,
						pusb_tx_aggr);
			} else
				wlan_usb_tx_copy_buf_to_aggr(pmadapter,
							     pmbuf_aggr,
							     pmbuf_curr,
							     pusb_tx_aggr);
			pmbuf_curr = pmbuf_next;
		}
		DBG_HEXDUMP(MIF_D, "USB AggrTx",
			    pmbuf_aggr->pbuf + pmbuf_aggr->data_offset,
			    pmbuf_aggr->data_len);
	}
	return pmbuf_aggr;
}

/**
 *  @brief Link buffer into aggregate head buffer
 *
 *  @param pmbuf_aggr	Pointer to aggregation buffer
 *  @param pmbuf		Pointer to buffer to add to the buffer list
 *  @param pusb_tx_aggr Pointer to usb_tx_aggr_params
 */
static inline t_void
wlan_usb_tx_link_buf_to_aggr(pmlan_buffer pmbuf_aggr, pmlan_buffer pmbuf,
			     usb_tx_aggr_params *pusb_tx_aggr)
{
	/* link new buf at end of list */
	pmbuf->pnext = pmbuf_aggr;
	pmbuf->pprev = pmbuf_aggr->pprev;
	pmbuf->pparent = pmbuf_aggr;
	pmbuf_aggr->pprev->pnext = pmbuf;
	pmbuf_aggr->pprev = pmbuf;
	pmbuf_aggr->use_count++;
	pusb_tx_aggr->aggr_len =
		usb_tx_aggr_pad_len(pusb_tx_aggr->aggr_len, pusb_tx_aggr);
	pusb_tx_aggr->aggr_len += pmbuf->data_len;
}

/**
 *  @brief Send aggregated buffer
 *
 *  @param pmadapter	Pointer to mlan_adapter structure
 *  @param pusb_tx_aggr Pointer to usb_tx_aggr_params
 */
static inline t_void wlan_usb_tx_send_aggr(pmlan_adapter pmadapter,
					   usb_tx_aggr_params *pusb_tx_aggr)
{
	mlan_status ret;
	pmlan_buffer pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr;
	ENTER();
	if (!pusb_tx_aggr->pmbuf_aggr) {
		LEAVE();
		return;
	}

	if (pusb_tx_aggr->pmbuf_aggr->use_count) {
		pmbuf_aggr = wlan_usb_copy_buf_to_aggr(pmadapter, pusb_tx_aggr);
		/* allocate new buffer for aggregation if not exist */
		if (!pmbuf_aggr) {
			PRINTM(MERROR,
			       "Error allocating [usb_tx] aggr mlan_buffer.\n");
			pmadapter->dbg.num_tx_host_to_card_failure +=
				pusb_tx_aggr->pmbuf_aggr->use_count;
			wlan_write_data_complete(pmadapter,
						 pusb_tx_aggr->pmbuf_aggr,
						 MLAN_STATUS_FAILURE);
			pusb_tx_aggr->pmbuf_aggr = MNULL;
			pusb_tx_aggr->aggr_len = 0;
			LEAVE();
			return;
		} else {
			wlan_write_data_complete(pmadapter,
						 pusb_tx_aggr->pmbuf_aggr,
						 MLAN_STATUS_SUCCESS);
			pusb_tx_aggr->pmbuf_aggr = MNULL;
			pusb_tx_aggr->aggr_len = 0;
		}
	} else if (pusb_tx_aggr->aggr_ctrl.aggr_mode ==
		   MLAN_USB_AGGR_MODE_LEN_V2) {
		t_u8 *payload = pmbuf_aggr->pbuf + pmbuf_aggr->data_offset;
		*(t_u16 *)&payload[0] = wlan_cpu_to_le16(pmbuf_aggr->data_len);
		*(t_u16 *)&payload[2] =
			wlan_cpu_to_le16(MLAN_TYPE_AGGR_DATA_V2 | 0x80);
		PRINTM(MIF_D, "USB Send single packet len=%d\n",
		       pmbuf_aggr->data_len);
		DBG_HEXDUMP(MIF_D, "USB Tx",
			    pmbuf_aggr->pbuf + pmbuf_aggr->data_offset,
			    pmbuf_aggr->data_len);
	}

	if (pmbuf_aggr && pmbuf_aggr->data_len) {
		pmadapter->data_sent = MTRUE;
		ret = pmadapter->callbacks.moal_write_data_async(
			pmadapter->pmoal_handle, pmbuf_aggr,
			pusb_tx_aggr->port);
		switch (ret) {
		case MLAN_STATUS_PRESOURCE:
			PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n");
			break;
		case MLAN_STATUS_RESOURCE:
			/* Shouldn't reach here due to next condition. */
			/* TODO: (maybe) How to requeue the aggregate? */
			/* It may occur when the pending tx urbs reach the high
			 * mark */
			/* Thus, block further pkts for a bit */
			PRINTM(MERROR,
			       "Error: moal_write_data_async failed: 0x%X\n",
			       ret);
			pmadapter->dbg.num_tx_host_to_card_failure++;
			pmbuf_aggr->status_code = MLAN_ERROR_DATA_TX_FAIL;
			wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
			break;
		case MLAN_STATUS_FAILURE:
			pmadapter->data_sent = MFALSE;
			PRINTM(MERROR,
			       "Error: moal_write_data_async failed: 0x%X\n",
			       ret);
			pmadapter->dbg.num_tx_host_to_card_failure++;
			pmbuf_aggr->status_code = MLAN_ERROR_DATA_TX_FAIL;
			wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
			break;
		case MLAN_STATUS_PENDING:
			pmadapter->data_sent = MFALSE;
			break;
		case MLAN_STATUS_SUCCESS:
			wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
			break;
		default:
			break;
		}

		/* aggr_buf now sent to bus, prevent others from using it */
		pusb_tx_aggr->pmbuf_aggr = MNULL;
		pusb_tx_aggr->aggr_len = 0;
	}
	LEAVE();
}

/********************************************************
			Global Functions
********************************************************/

/**
 *	@brief This function get pcie device from card type
 *
 *	@param pmadapter  A pointer to mlan_adapter structure
 *	@return 		  MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
mlan_status wlan_get_usb_device(pmlan_adapter pmadapter)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	t_u16 card_type = pmadapter->card_type;

	ENTER();

	ret = pmadapter->callbacks.moal_malloc(pmadapter->pmoal_handle,
					       sizeof(mlan_usb_card),
					       MLAN_MEM_DEF,
					       (t_u8 **)&pmadapter->pcard_usb);
	if (ret != MLAN_STATUS_SUCCESS || !pmadapter->pcard_usb) {
		PRINTM(MERROR, "Failed to allocate pcard_usb\n");
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}

	switch (card_type) {
#ifdef USB8801
	case CARD_TYPE_USB8801:
		pmadapter->pcard_info = &mlan_card_info_usb8801;
		break;
#endif
#ifdef USB8897
	case CARD_TYPE_USB8897:
		pmadapter->pcard_info = &mlan_card_info_usb8897;
		break;
#endif
#ifdef USB8997
	case CARD_TYPE_USB8997:
		pmadapter->pcard_info = &mlan_card_info_usb8997;
		break;
#endif
#ifdef USB8978
	case CARD_TYPE_USB8978:
		pmadapter->pcard_info = &mlan_card_info_usb8978;
		break;
#endif
#ifdef USB9098
	case CARD_TYPE_USB9098:
		pmadapter->pcard_info = &mlan_card_info_usb9098;
		break;
#endif
#ifdef USB9097
	case CARD_TYPE_USB9097:
		pmadapter->pcard_info = &mlan_card_info_usb9097;
		break;
#endif
	default:
		PRINTM(MERROR, "can't get right USB card type \n");
		ret = MLAN_STATUS_FAILURE;
		break;
	}

	LEAVE();
	return ret;
}

/**
 *  @brief  This function downloads firmware to card
 *
 *  @param pmadapter	A pointer to mlan_adapter
 *  @param pmfw			A pointer to firmware image
 *
 *  @return		MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status wlan_usb_dnld_fw(pmlan_adapter pmadapter,
				    pmlan_fw_image pmfw)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;

	ENTER();

	ret = wlan_usb_prog_fw_w_helper(pmadapter, pmfw);
	if (ret != MLAN_STATUS_SUCCESS) {
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}

	LEAVE();
	return ret;
}

/**
 *  @brief  This function deaggregates USB RX Data Packet from device
 *
 *  @param pmadapter	A pointer to mlan_adapter
 *  @param pmbuf		A pointer to the received buffer
 *
 *  @return		MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
mlan_status wlan_usb_deaggr_rx_pkt(pmlan_adapter pmadapter, pmlan_buffer pmbuf)
{
	const t_u8 zero_rx_pd[sizeof(RxPD)] = {0};
	mlan_status ret = MLAN_STATUS_SUCCESS;
	t_u32 curr_pkt_len;
	RxPD *prx_pd;
	t_u8 *pdata;
	t_s32 aggr_len;
	pmlan_buffer pdeaggr_buf;

	ENTER();

	pdata = pmbuf->pbuf + pmbuf->data_offset;
	prx_pd = (RxPD *)pdata;
	curr_pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) +
		       wlan_le16_to_cpu(prx_pd->rx_pkt_offset);
	/* if non-aggregate, just send through, don’t process here */
	aggr_len = pmbuf->data_len;
	if ((aggr_len == (t_s32)curr_pkt_len) ||
	    (wlan_usb_deaggr_rx_num_pkts(pmadapter, pdata, aggr_len) == 1) ||
	    (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.enable != MTRUE)) {
		ret = wlan_handle_rx_packet(pmadapter, pmbuf);
		LEAVE();
		return ret;
	}

	while (aggr_len >= (t_s32)sizeof(RxPD)) {
		/* check for (all-zeroes) termination RxPD */
		if (!memcmp(pmadapter, pdata, zero_rx_pd, sizeof(RxPD))) {
			break;
		}

		/* make new buffer and copy packet to it (including RxPD).
		 * Also, reserve headroom so that there must have space
		 * to change RxPD to TxPD for bridge packet in uAP mode */
		pdeaggr_buf = wlan_alloc_mlan_buffer(pmadapter, curr_pkt_len,
						     MLAN_RX_HEADER_LEN,
						     MOAL_ALLOC_MLAN_BUFFER);
		if (pdeaggr_buf == MNULL) {
			PRINTM(MERROR,
			       "Error allocating [usb_rx] deaggr mlan_buffer\n");
			ret = MLAN_STATUS_FAILURE;
			break;
		}
		pdeaggr_buf->bss_index = pmbuf->bss_index;
		pdeaggr_buf->buf_type = pmbuf->buf_type;
		pdeaggr_buf->data_len = curr_pkt_len;
		pdeaggr_buf->in_ts_sec = pmbuf->in_ts_sec;
		pdeaggr_buf->in_ts_usec = pmbuf->in_ts_usec;
		pdeaggr_buf->priority = pmbuf->priority;
		memcpy_ext(pmadapter,
			   pdeaggr_buf->pbuf + pdeaggr_buf->data_offset, pdata,
			   curr_pkt_len, pdeaggr_buf->data_len);

		/* send new packet to processing */
		ret = wlan_handle_rx_packet(pmadapter, pdeaggr_buf);
		if (ret == MLAN_STATUS_FAILURE) {
			break;
		}
		/* last block has no padding bytes */
		if (aggr_len == (t_s32)curr_pkt_len) {
			break;
		}

		/* round up to next block boundary */
		if (curr_pkt_len %
		    pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_align)
			curr_pkt_len += (pmadapter->pcard_usb->usb_rx_deaggr
						 .aggr_ctrl.aggr_align -
					 (curr_pkt_len %
					  pmadapter->pcard_usb->usb_rx_deaggr
						  .aggr_ctrl.aggr_align));
		/* point to next packet */
		aggr_len -= curr_pkt_len;
		pdata += curr_pkt_len;
		prx_pd = (RxPD *)pdata;
		curr_pkt_len = wlan_le16_to_cpu(prx_pd->rx_pkt_length) +
			       wlan_le16_to_cpu(prx_pd->rx_pkt_offset);
	}

	/* free original pmbuf (since not sent for processing) */
	pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf,
						pmadapter->rx_data_ep, ret);
	LEAVE();
	return ret;
}

/**
 *  @brief This function restore tx_pause flag
 *
 *  @param pmadapter    A pointer to mlan_adapter structure
 *  @param pusb_tx_aggr A pointer to usb_tx_aggr_params
 *
 *  @return             MTRUE/MFALSE
 */
static t_u8 wlan_is_port_tx_paused(pmlan_adapter pmadapter,
				   usb_tx_aggr_params *pusb_tx_aggr)
{
	mlan_private *pmpriv = MNULL;
	t_u8 i;
	t_u8 ret = MFALSE;
	for (i = 0; i < pmadapter->priv_num; i++) {
		pmpriv = pmadapter->priv[i];
		if (pmpriv && pmpriv->tx_pause &&
		    (pmpriv->port == pusb_tx_aggr->port)) {
			ret = MTRUE;
			break;
		}
	}
	return ret;
}

/**
 *  @brief This function handles the timeout of usb tx aggregation.
 *  It will send the aggregate buffer being held.
 *
 *  @param function_context   A pointer to function_context
 *  @return 	   N/A
 */
t_void wlan_usb_tx_aggr_timeout_func(t_void *function_context)
{
	usb_tx_aggr_params *pusb_tx_aggr =
		(usb_tx_aggr_params *)function_context;
	pmlan_adapter pmadapter = (mlan_adapter *)pusb_tx_aggr->phandle;
	pmlan_callbacks pcb = &pmadapter->callbacks;

	ENTER();
	pcb->moal_spin_lock(pmadapter->pmoal_handle, pusb_tx_aggr->paggr_lock);
	pusb_tx_aggr->aggr_hold_timer_is_set = MFALSE;
	if (pusb_tx_aggr->pmbuf_aggr && !pmadapter->data_sent &&
	    !wlan_is_port_tx_paused(pmadapter, pusb_tx_aggr))
		wlan_usb_tx_send_aggr(pmadapter, pusb_tx_aggr);
	pcb->moal_spin_unlock(pmadapter->pmoal_handle,
			      pusb_tx_aggr->paggr_lock);
	LEAVE();
}

/**
 *  @brief  This function aggregates USB TX Data Packet to send to device
 *
 *  @param pmadapter	A pointer to mlan_adapter
 *  @param pmbuf		A pointer to the transmit buffer
 *  @param tx_param 	A pointer to mlan_tx_param
 *  @param pusb_tx_aggr A pointer to usb_tx_aggr_params
 *
 *  @return		MLAN_STATUS_PENDING or MLAN_STATUS_FAILURE
 */
/*
 *  Non Scatter-Gather code creates a new large buffer where each incoming
 *     buffer's data contents are copied to (aligned to USB boundaries).
 *  The individual buffers are ALSO linked to the large buffer,
 *    in order to handle complete AFTER the aggregate is sent.
 *  pmbuf_aggr->data_len is used to keep track of bytes aggregated so far.
 */
mlan_status wlan_usb_host_to_card_aggr(pmlan_adapter pmadapter,
				       pmlan_buffer pmbuf,
				       mlan_tx_param *tx_param,
				       usb_tx_aggr_params *pusb_tx_aggr)
{
	pmlan_callbacks pcb = &pmadapter->callbacks;
	pmlan_buffer pmbuf_aggr;
	mlan_status ret = MLAN_STATUS_PENDING;
	t_u32 next_pkt_len = (tx_param) ? tx_param->next_pkt_len : 0;
	t_u32 aggr_len_counter = 0;
	/* indicators */
	t_u8 f_precopy_cur_buf = 0;
	t_u8 f_send_aggr_buf = 0;
	t_u8 f_postcopy_cur_buf = 0;
	t_u32 max_aggr_size = 0, max_aggr_num = 0;

	ENTER();

	pcb->moal_spin_lock(pmadapter->pmoal_handle, pusb_tx_aggr->paggr_lock);

	/* stop timer while we process */
	if (pusb_tx_aggr->aggr_hold_timer_is_set) {
		pcb->moal_stop_timer(pmadapter->pmoal_handle,
				     pusb_tx_aggr->paggr_hold_timer);
		pusb_tx_aggr->aggr_hold_timer_is_set = MFALSE;
	}

	pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr;

	if (pusb_tx_aggr->aggr_ctrl.aggr_tmo == MLAN_USB_TX_AGGR_TIMEOUT_DYN) {
		if (!pmbuf_aggr) {
			/* Start aggr from min timeout value in micro sec */
			pusb_tx_aggr->hold_timeout_msec =
				MLAN_USB_TX_MIN_AGGR_TIMEOUT;
		} else {
			/* Increase timeout in milisecond if pkts are
			 * consecutive */
			if (pusb_tx_aggr->hold_timeout_msec <
			    MLAN_USB_TX_MAX_AGGR_TIMEOUT)
				pusb_tx_aggr->hold_timeout_msec++;
		}
	} else {
		if (pusb_tx_aggr->aggr_ctrl.aggr_tmo)
			pusb_tx_aggr->hold_timeout_msec =
				pusb_tx_aggr->aggr_ctrl.aggr_tmo / 1000;
	}

	max_aggr_size = max_aggr_num = pusb_tx_aggr->aggr_ctrl.aggr_max;
	if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_NUM) {
		max_aggr_size *= MAX(MLAN_USB_MAX_PKT_SIZE,
				     pusb_tx_aggr->aggr_ctrl.aggr_align);
	}
	if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_LEN)
		max_aggr_num /= pusb_tx_aggr->aggr_ctrl.aggr_align;
	else if (pusb_tx_aggr->aggr_ctrl.aggr_mode == MLAN_USB_AGGR_MODE_LEN_V2)
		max_aggr_num = MLAN_USB_TX_AGGR_MAX_NUM;
	if (!pmbuf_aggr) {
		/* use this buf to start linked list, that's it */
		pmbuf->pnext = pmbuf->pprev = pmbuf;
		pmbuf_aggr = pmbuf;
		pusb_tx_aggr->pmbuf_aggr = pmbuf_aggr;
		pusb_tx_aggr->aggr_len = pmbuf->data_len;
		pmbuf->flags |= MLAN_BUF_FLAG_USB_TX_AGGR;

	} else {
		/* DECIDE what to do */
		aggr_len_counter = usb_tx_aggr_pad_len(pusb_tx_aggr->aggr_len,
						       pusb_tx_aggr);

		if ((aggr_len_counter + pmbuf->data_len) < max_aggr_size) {
			f_precopy_cur_buf = 1; /* can fit current packet in aggr
						*/
			if (next_pkt_len) {
				aggr_len_counter += usb_tx_aggr_pad_len(
					pmbuf->data_len, pusb_tx_aggr);
				if ((aggr_len_counter + next_pkt_len) >=
				    max_aggr_size)
					f_send_aggr_buf = 1; /* can't fit next
								packet, send now
							      */
			}
		} else {
			/* can't fit current packet */
			if (pusb_tx_aggr->aggr_len)
				f_send_aggr_buf = 1; /* send aggr first */
			f_postcopy_cur_buf = 1; /* then copy into new aggr_buf
						 */
		}
	}

	/* For zero timeout and zero next packet length send pkt now */
	if (!pusb_tx_aggr->aggr_ctrl.aggr_tmo && !next_pkt_len)
		f_send_aggr_buf = 1;

	/* PERFORM ACTIONS as decided */
	if (f_precopy_cur_buf) {
		PRINTM(MIF_D, "%s: Precopy current buffer.\n", __FUNCTION__);
		wlan_usb_tx_link_buf_to_aggr(pmbuf_aggr, pmbuf, pusb_tx_aggr);
	}
	if (pmbuf_aggr->use_count + 1 >= max_aggr_num)
		f_send_aggr_buf = 1;

	if (pmbuf->flags & MLAN_BUF_FLAG_NULL_PKT ||
	    pmbuf->flags & MLAN_BUF_FLAG_TCP_ACK)
		f_send_aggr_buf = 1;

	if (f_send_aggr_buf) {
		PRINTM(MIF_D, "%s: Send aggregate buffer.\n", __FUNCTION__);
		wlan_usb_tx_send_aggr(pmadapter, pusb_tx_aggr);
		pmbuf_aggr = pusb_tx_aggr->pmbuf_aggr; /* update ptr */
	}

	if (f_postcopy_cur_buf) {
		PRINTM(MIF_D, "%s: Postcopy current buffer.\n", __FUNCTION__);
		if (!pmbuf_aggr) { /* this is possible if just sent (above) */
			/* use this buf to start linked list */
			pmbuf->pnext = pmbuf->pprev = pmbuf;
			pmbuf_aggr = pmbuf;
			pusb_tx_aggr->pmbuf_aggr = pmbuf_aggr;
			pusb_tx_aggr->aggr_len = pmbuf->data_len;
			pmbuf->flags |= MLAN_BUF_FLAG_USB_TX_AGGR;
		}
	}
	/* (re)start timer if there is something in the aggregation buffer */
	if (pmbuf_aggr && pmbuf_aggr->data_len) {
		if (pusb_tx_aggr->aggr_ctrl.aggr_tmo) {
			pcb->moal_start_timer(pmadapter->pmoal_handle,
					      pusb_tx_aggr->paggr_hold_timer,
					      MFALSE,
					      pusb_tx_aggr->hold_timeout_msec);
			pusb_tx_aggr->aggr_hold_timer_is_set = MTRUE;
		}
	}

	pcb->moal_spin_unlock(pmadapter->pmoal_handle,
			      pusb_tx_aggr->paggr_lock);
	LEAVE();
	return ret;
}

/**
 *  @brief This function wakes up the card.
 *
 *  @param pmadapter		A pointer to mlan_adapter structure
 *  @param timeout          set timeout flag
 *
 *  @return			MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status wlan_pm_usb_wakeup_card(pmlan_adapter pmadapter,
					   t_u8 timeout)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	t_u32 age_ts_usec;

	ENTER();
	PRINTM(MEVENT, "Wakeup device...\n");
	pmadapter->callbacks.moal_get_system_time(pmadapter->pmoal_handle,
						  &pmadapter->pm_wakeup_in_secs,
						  &age_ts_usec);

	/* Simulation of HS_AWAKE event */
	pmadapter->pm_wakeup_fw_try = MFALSE;
	pmadapter->pm_wakeup_card_req = MFALSE;
	/* TODO USB suspend/resume */
	pmadapter->ps_state = PS_STATE_AWAKE;
	LEAVE();
	return ret;
}

/**
 *  @brief This function downloads data from driver to card.
 *
 *  Both commands and data packets are transferred to the card
 *  by this function. This function adds the PCIE specific header
 *  to the front of the buffer before transferring. The header
 *  contains the length of the packet and the type. The firmware
 *  handles the packets based upon this set type.
 *
 *  @param pmpriv    A pointer to pmlan_private structure
 *  @param type      data or command
 *  @param pmbuf     A pointer to mlan_buffer (pmbuf->data_len should include
 * PCIE header)
 *  @param tx_param  A pointer to mlan_tx_param (can be MNULL if type is
 * command)
 *
 *  @return          MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
static mlan_status wlan_usb_host_to_card(pmlan_private pmpriv, t_u8 type,
					 mlan_buffer *pmbuf,
					 mlan_tx_param *tx_param)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	usb_tx_aggr_params *pusb_tx_aggr = MNULL;
	pmlan_adapter pmadapter = pmpriv->adapter;

	ENTER();

	if (!pmbuf) {
		PRINTM(MERROR, "Passed NULL pmbuf to %s\n", __FUNCTION__);
		return MLAN_STATUS_FAILURE;
	}
	if (type == MLAN_TYPE_CMD
#if (defined(USB9098) || defined(USB9097))
	    || type == MLAN_TYPE_VDLL
#endif
	) {
		pmadapter->cmd_sent = MTRUE;
		ret = pmadapter->callbacks.moal_write_data_async(
			pmadapter->pmoal_handle, pmbuf, pmadapter->tx_cmd_ep);
		if (ret == MLAN_STATUS_FAILURE)
			pmadapter->cmd_sent = MFALSE;
		LEAVE();
		return ret;
	}
	pusb_tx_aggr = wlan_get_usb_tx_aggr_params(pmadapter, pmpriv->port);
	if (pusb_tx_aggr) {
		ret = wlan_usb_host_to_card_aggr(pmadapter, pmbuf, tx_param,
						 pusb_tx_aggr);
	} else {
		pmadapter->data_sent = MTRUE;
		ret = pmadapter->callbacks.moal_write_data_async(
			pmadapter->pmoal_handle, pmbuf, pmpriv->port);
		switch (ret) {
		case MLAN_STATUS_PRESOURCE:
			PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n");
			break;
		case MLAN_STATUS_RESOURCE:

			break;
		case MLAN_STATUS_FAILURE:
			pmadapter->data_sent = MFALSE;
			break;
		case MLAN_STATUS_PENDING:
			pmadapter->data_sent = MFALSE;
			break;
		case MLAN_STATUS_SUCCESS:
			break;
		default:
			break;
		}
	}

	LEAVE();
	return ret;
}

/**
 *  @brief This function handle event/cmd complete
 *
 *  @param pmadapter A pointer to mlan_adapter structure
 *  @param pmbuf     A pointer to the mlan_buffer
 *  @return          N/A
 */
static mlan_status wlan_usb_cmdevt_complete(pmlan_adapter pmadapter,
					    mlan_buffer *pmbuf,
					    mlan_status status)
{
	ENTER();

	pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf,
						pmadapter->rx_cmd_ep, status);

	LEAVE();
	return MLAN_STATUS_SUCCESS;
}

/**
 *  @brief This function handle data complete
 *
 *  @param pmadapter A pointer to mlan_adapter structure
 *  @param pmbuf     A pointer to the mlan_buffer
 *  @return          N/A
 */
static mlan_status wlan_usb_data_complete(pmlan_adapter pmadapter,
					  mlan_buffer *pmbuf,
					  mlan_status status)
{
	ENTER();

	pmadapter->callbacks.moal_recv_complete(pmadapter->pmoal_handle, pmbuf,
						pmadapter->rx_data_ep, status);

	LEAVE();
	return MLAN_STATUS_SUCCESS;
}

/**
 *  @brief This function handle receive packet
 *
 *  @param pmadapter A pointer to mlan_adapter structure
 *  @param pmbuf     A pointer to the mlan_buffer
 *  @return
 */
static mlan_status wlan_usb_handle_rx_packet(mlan_adapter *pmadapter,
					     pmlan_buffer pmbuf)
{
	ENTER();

	if (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.enable == MTRUE)
		return wlan_usb_deaggr_rx_pkt(pmadapter, pmbuf);
	else
		return wlan_handle_rx_packet(pmadapter, pmbuf);

	LEAVE();
}

mlan_adapter_operations mlan_usb_ops = {
	.dnld_fw = wlan_usb_dnld_fw,
	.host_to_card = wlan_usb_host_to_card,
	.wakeup_card = wlan_pm_usb_wakeup_card,
	.event_complete = wlan_usb_cmdevt_complete,
	.data_complete = wlan_usb_data_complete,
	.cmdrsp_complete = wlan_usb_cmdevt_complete,
	.handle_rx_packet = wlan_usb_handle_rx_packet,

	.intf_header_len = USB_INTF_HEADER_LEN,
};