/* SPDX-License-Identifier: GPL-2.0 */
/******************************************************************************
 *
 * Copyright(c) 2015 - 2016 Realtek Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 *
 ******************************************************************************/
#define _RTL8821CE_XMIT_C_

#include <drv_types.h>		/* PADAPTER, rtw_xmit.h and etc. */
#include <hal_data.h>		/* HAL_DATA_TYPE */
#include "../halmac/halmac_api.h"
#include "../rtl8821c.h"
#include "rtl8821ce.h"

/* Debug Buffer Descriptor Ring */
#define buf_desc_debug(...)  do {} while (0)

static void rtl8821ce_xmit_tasklet(void *priv)
{
	_adapter *padapter = (_adapter *)priv;

	/* try to deal with the pending packets */
	rtl8821ce_xmitframe_resume(padapter);
}

s32 rtl8821ce_init_xmit_priv(_adapter *padapter)
{
	s32 ret = _SUCCESS;
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);

	_rtw_spinlock_init(&pdvobjpriv->irq_th_lock);

	tasklet_init(&pxmitpriv->xmit_tasklet,
		     (void(*)(unsigned long))rtl8821ce_xmit_tasklet,
		     (unsigned long)padapter);

	return ret;
}

void rtl8821ce_free_xmit_priv(_adapter *padapter)
{
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);

	_rtw_spinlock_free(&pdvobjpriv->irq_th_lock);
}

static s32 rtl8821ce_enqueue_xmitbuf(struct rtw_tx_ring	*ring,
				     struct xmit_buf *pxmitbuf)
{
	_queue *ppending_queue = &ring->queue;

	if (pxmitbuf == NULL)
		return _FAIL;

	rtw_list_delete(&pxmitbuf->list);
	rtw_list_insert_tail(&(pxmitbuf->list), get_list_head(ppending_queue));
	ring->qlen++;

	return _SUCCESS;
}

struct xmit_buf *rtl8821ce_dequeue_xmitbuf(struct rtw_tx_ring	*ring)
{
	_list *plist, *phead;
	struct xmit_buf *pxmitbuf =  NULL;
	_queue *ppending_queue = &ring->queue;

	if (_rtw_queue_empty(ppending_queue) == _TRUE)
		pxmitbuf = NULL;
	else {

		phead = get_list_head(ppending_queue);
		plist = get_next(phead);
		pxmitbuf = LIST_CONTAINOR(plist, struct xmit_buf, list);
		rtw_list_delete(&(pxmitbuf->list));
		ring->qlen--;
	}

	return pxmitbuf;
}

static u8 *get_txbd(_adapter *padapter, u8 q_idx)
{
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct rtw_tx_ring *ring;
	u8 *ptxbd = NULL;
	int idx = 0;

	ring = &pxmitpriv->tx_ring[q_idx];

	/* DO NOT use last entry. */
	/* (len -1) to avoid wrap around overlap problem in cycler queue. */
	if (ring->qlen == (ring->entries - 1)) {
		RTW_INFO("No more TX desc@%d, ring->idx = %d,idx = %d\n",
			 q_idx, ring->idx, idx);
		return NULL;
	}

	if (q_idx == BCN_QUEUE_INX)
		idx = 0;
	else
		idx = (ring->idx + ring->qlen) % ring->entries;

	ptxbd = (u8 *)&ring->buf_desc[idx];

	return ptxbd;
}

/*
 * Get txbd reg addr according to q_sel
 */
static u16 get_txbd_rw_reg(u16 q_idx)
{
	u16 txbd_reg_addr = REG_BEQ_TXBD_IDX;

	switch (q_idx) {

	case BK_QUEUE_INX:
		txbd_reg_addr = REG_BKQ_TXBD_IDX;
		break;

	case BE_QUEUE_INX:
		txbd_reg_addr = REG_BEQ_TXBD_IDX;
		break;

	case VI_QUEUE_INX:
		txbd_reg_addr = REG_VIQ_TXBD_IDX;
		break;

	case VO_QUEUE_INX:
		txbd_reg_addr = REG_VOQ_TXBD_IDX;
		break;

	case BCN_QUEUE_INX:
		txbd_reg_addr = REG_BEQ_TXBD_IDX;	/* need check */
		break;

	case TXCMD_QUEUE_INX:
		txbd_reg_addr = REG_H2CQ_TXBD_IDX;
		break;

	case MGT_QUEUE_INX:
		txbd_reg_addr = REG_MGQ_TXBD_IDX;
		break;

	case HIGH_QUEUE_INX:
		txbd_reg_addr = REG_HI0Q_TXBD_IDX;   /* need check */
		break;

	default:
		break;
	}

	return txbd_reg_addr;
}

struct xmit_frame *__rtw_alloc_cmdxmitframe_8821ce(struct xmit_priv *pxmitpriv,
		enum cmdbuf_type buf_type)
{
	_adapter *padapter;
	u8 *ptxdesc = NULL;

	padapter = GET_PRIMARY_ADAPTER(pxmitpriv->adapter);

	ptxdesc = get_txbd(padapter, BCN_QUEUE_INX);

	/* set OWN bit in Beacon tx descriptor */
	if (ptxdesc != NULL)
		SET_TX_BD_OWN(ptxdesc, 0);
	else
		return NULL;

	return __rtw_alloc_cmdxmitframe(pxmitpriv, CMDBUF_BEACON);
}

/*
 * Update Read/Write pointer
 *	Read pointer is h/w descriptor index
 *	Write pointer is host desciptor index:
 *	For tx side, if own bit is set in packet index n,
 *	host pointer (write pointer) point to index n + 1.)
 */
void fill_txbd_own(_adapter *padapter, u8 *txbd, u16 queue_idx,
	struct rtw_tx_ring *ptxring)
{
	u16 host_wp = 0;

	if (queue_idx == BCN_QUEUE_INX) {

		SET_TX_BD_OWN(txbd, 1);

		/* kick start */
		rtw_write8(padapter, REG_RX_RXBD_NUM + 1,
		rtw_read8(padapter, REG_RX_RXBD_NUM + 1) | BIT(4));

		return;
	}

	/*
	 * update h/w index
	 * for tx side, if own bit is set in packet index n,
	 * host pointer (write pointer) point to index n + 1.
	 */

	/* for current tx packet, enqueue has been ring->qlen++ before.
	 * so, host_wp = ring->idx + ring->qlen.
	 */
	host_wp = (ptxring->idx + ptxring->qlen) % ptxring->entries;
	rtw_write16(padapter, get_txbd_rw_reg(queue_idx), host_wp);
}

/*
 * Fill tx buffer desciptor. Map each buffer address in tx buffer descriptor
 * segment. Designed for tx buffer descriptor architecture
 * Input *pmem: pointer to the Tx Buffer Descriptor
 */
static void rtl8821ce_update_txbd(struct xmit_frame *pxmitframe,
				  u8 *txbd, s32 sz)
{
	_adapter *padapter = pxmitframe->padapter;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);
	dma_addr_t mapping;
	u32 i = 0;
	u16 seg_num =
		((TX_BUFFER_SEG_NUM == 0) ? 2 : ((TX_BUFFER_SEG_NUM == 1) ? 4 : 8));
	u16 tx_page_size_reg = 1;
	u16 page_size_length = 0;

	/* map TX DESC buf_addr (including TX DESC + tx data) */
	mapping = pci_map_single(pdvobjpriv->ppcidev, pxmitframe->buf_addr,
				 sz + TX_WIFI_INFO_SIZE, PCI_DMA_TODEVICE);

	/* Calculate page size.
	 * Total buffer length including TX_WIFI_INFO and PacketLen
	 */
	if (tx_page_size_reg > 0) {
		page_size_length = (sz + TX_WIFI_INFO_SIZE) /
				   (tx_page_size_reg * 128);
		if (((sz + TX_WIFI_INFO_SIZE) % (tx_page_size_reg * 128)) > 0)
			page_size_length++;
	}

	/*
	 * Reset all tx buffer desciprtor content
	 * -- Reset first element
	 */
	SET_TX_BD_TX_BUFF_SIZE0(txbd, 0);
	SET_TX_BD_PSB(txbd, 0);
	SET_TX_BD_OWN(txbd, 0);

	/* -- Reset second and other element */
	for (i = 1 ; i < seg_num ; i++) {
		SET_TXBUFFER_DESC_LEN_WITH_OFFSET(txbd, i, 0);
		SET_TXBUFFER_DESC_AMSDU_WITH_OFFSET(txbd, i, 0);
		SET_TXBUFFER_DESC_ADD_LOW_WITH_OFFSET(txbd, i, 0);
	}

	/*
	 * Fill buffer length of the first buffer,
	 * For 8821ce, it is required that TX_WIFI_INFO is put in first segment,
	 * and the size of the first segment cannot be larger than
	 * TX_WIFI_INFO_SIZE.
	 */
	SET_TX_BD_TX_BUFF_SIZE0(txbd, TX_WIFI_INFO_SIZE);
	SET_TX_BD_PSB(txbd, page_size_length);
	/* starting addr of TXDESC */
	SET_TX_BD_PHYSICAL_ADDR0_LOW(txbd, mapping);

	/*
	 * It is assumed that in linux implementation, packet is coalesced
	 * in only one buffer. Extension mode is not supported here
	 */
	SET_TXBUFFER_DESC_LEN_WITH_OFFSET(txbd, 1, sz);
	/* don't using extendsion mode. */
	SET_TXBUFFER_DESC_AMSDU_WITH_OFFSET(txbd, 1, 0);
	SET_TXBUFFER_DESC_ADD_LOW_WITH_OFFSET(txbd, 1,
				      mapping + TX_WIFI_INFO_SIZE); /* pkt */

	/*buf_desc_debug("TX:%s, txbd = 0x%p\n", __FUNCTION__, txbd);*/
	buf_desc_debug("%s, txbd = 0x%08x\n", __func__, txbd);
	buf_desc_debug("TXBD:, 00h(0x%08x)\n", *((u32 *)(txbd)));
	buf_desc_debug("TXBD:, 04h(0x%08x)\n", *((u32 *)(txbd + 4)));
	buf_desc_debug("TXBD:, 08h(0x%08x)\n", *((u32 *)(txbd + 8)));
	buf_desc_debug("TXBD:, 12h(0x%08x)\n", *((u32 *)(txbd + 12)));

}

static s32 update_txdesc(struct xmit_frame *pxmitframe, s32 sz)
{
	_adapter *padapter = pxmitframe->padapter;
	struct pkt_attrib *pattrib = &pxmitframe->attrib;
	HAL_DATA_TYPE *pHalData = GET_HAL_DATA(padapter);
	struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv;
	struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info);
	u8 *ptxdesc;
	sint bmcst = IS_MCAST(pattrib->ra);
	u16 SWDefineContent = 0x0;
	u8 DriverFixedRate = 0x0;

	ptxdesc = pxmitframe->buf_addr;
	_rtw_memset(ptxdesc, 0, TXDESC_SIZE);

	/* offset 0 */
	/*SET_TX_DESC_FIRST_SEG_8812(ptxdesc, 1);*/
	SET_TX_DESC_LS_8821C(ptxdesc, 1);
	/*SET_TX_DESC_OWN_8812(ptxdesc, 1);*/

	SET_TX_DESC_TXPKTSIZE_8821C(ptxdesc, sz);
	SET_TX_DESC_OFFSET_8821C(ptxdesc, TXDESC_SIZE);


	if (bmcst)
		SET_TX_DESC_BMC_8821C(ptxdesc, 1);

	SET_TX_DESC_MACID_8821C(ptxdesc, pattrib->mac_id);
	SET_TX_DESC_RATE_ID_8821C(ptxdesc, pattrib->raid);

	SET_TX_DESC_QSEL_8821C(ptxdesc,  pattrib->qsel);

	if (!pattrib->qos_en) {
		/* Hw set sequence number */
		SET_TX_DESC_EN_HWSEQ_8821C(ptxdesc, 1);
		SET_TX_DESC_EN_HWEXSEQ_8821C(ptxdesc, 0);
		SET_TX_DESC_DISQSELSEQ_8821C(ptxdesc, 1);
		SET_TX_DESC_HW_SSN_SEL_8821C(ptxdesc, 0);
	} else
		SET_TX_DESC_SW_SEQ_8821C(ptxdesc, pattrib->seqnum);

	if ((pxmitframe->frame_tag & 0x0f) == DATA_FRAMETAG) {
		rtl8821c_fill_txdesc_sectype(pattrib, ptxdesc);
		rtl8821c_fill_txdesc_vcs(padapter, pattrib, ptxdesc);


		if ((pattrib->ether_type != 0x888e) &&
		    (pattrib->ether_type != 0x0806) &&
		    (pattrib->ether_type != 0x88b4) &&
		    (pattrib->dhcp_pkt != 1)
		   ) {
			/* Non EAP & ARP & DHCP type data packet */

			if (pattrib->ampdu_en == _TRUE) {
				/* 8821c does NOT support AGG broadcast pkt */
				if (!bmcst)
					SET_TX_DESC_AGG_EN_8821C(ptxdesc, 1);

				SET_TX_DESC_MAX_AGG_NUM_8821C(ptxdesc, 0x1f);
				/* Set A-MPDU aggregation. */
				SET_TX_DESC_AMPDU_DENSITY_8821C(ptxdesc,
							pattrib->ampdu_spacing);
			} else
				SET_TX_DESC_BK_8821C(ptxdesc, 1);

			rtl8821c_fill_txdesc_phy(padapter, pattrib, ptxdesc);

			/* DATA  Rate FB LMT */
			/* compatibility for MCC consideration,
			 * use pmlmeext->cur_channel
			 */
			if (pmlmeext->cur_channel > 14)
				/* for 5G. OFMD 6M */
				SET_TX_DESC_DATA_RTY_LOWEST_RATE_8821C(
					ptxdesc, 4);
			else
				/* for 2.4G. CCK 1M */
				SET_TX_DESC_DATA_RTY_LOWEST_RATE_8821C(
					ptxdesc, 0);

			if (pHalData->fw_ractrl == _FALSE) {
				SET_TX_DESC_USE_RATE_8821C(ptxdesc, 1);
				DriverFixedRate = 0x01;

				if (pHalData->INIDATA_RATE[pattrib->mac_id] &
				    BIT(7))
					SET_TX_DESC_DATA_SHORT_8821C(
						ptxdesc, 1);

				SET_TX_DESC_DATARATE_8821C(ptxdesc,
					pHalData->INIDATA_RATE[pattrib->mac_id]
							   & 0x7F);
			}

			if (padapter->fix_rate != 0xFF) {
				/* modify data rate by iwpriv */
				SET_TX_DESC_USE_RATE_8821C(ptxdesc, 1);

				DriverFixedRate = 0x01;
				if (padapter->fix_rate & BIT(7))
					SET_TX_DESC_DATA_SHORT_8821C(
						ptxdesc, 1);

				SET_TX_DESC_DATARATE_8821C(ptxdesc,
						   (padapter->fix_rate & 0x7F));
				if (!padapter->data_fb)
					SET_TX_DESC_DISDATAFB_8821C(ptxdesc, 1);
			}

			if (pattrib->ldpc)
				SET_TX_DESC_DATA_LDPC_8821C(ptxdesc, 1);
			if (pattrib->stbc)
				SET_TX_DESC_DATA_STBC_8821C(ptxdesc, 1);
		} else {
			/*
			 * EAP data packet and ARP packet and DHCP.
			 * Use the 1M data rate to send the EAP/ARP packet.
			 * This will maybe make the handshake smooth.
			 */

			SET_TX_DESC_USE_RATE_8821C(ptxdesc, 1);
			DriverFixedRate = 0x01;
			SET_TX_DESC_BK_8821C(ptxdesc, 1);

			/* HW will ignore this setting if the transmission rate
			 * is legacy OFDM.
			 */
			if (pmlmeinfo->preamble_mode == PREAMBLE_SHORT)
				SET_TX_DESC_DATA_SHORT_8821C(ptxdesc, 1);

			SET_TX_DESC_DATARATE_8821C(ptxdesc,
					   MRateToHwRate(pmlmeext->tx_rate));
		}

	} else if ((pxmitframe->frame_tag & 0x0f) == MGNT_FRAMETAG) {
		SET_TX_DESC_USE_RATE_8821C(ptxdesc, 1);
		DriverFixedRate = 0x01;

		SET_TX_DESC_DATARATE_8821C(ptxdesc, MRateToHwRate(pattrib->rate));

		SET_TX_DESC_RTY_LMT_EN_8821C(ptxdesc, 1);
		if (pattrib->retry_ctrl == _TRUE)
			SET_TX_DESC_RTS_DATA_RTY_LMT_8821C(ptxdesc, 6);
		else
			SET_TX_DESC_RTS_DATA_RTY_LMT_8821C(ptxdesc, 12);

		/*rtl8821c_fill_txdesc_mgnt_bf(pxmitframe, ptxdesc); Todo for 8821C*/

		/* CCX-TXRPT ack for xmit mgmt frames. */
		if (pxmitframe->ack_report) {
			SET_TX_DESC_SPE_RPT_8821C(ptxdesc, 1);
		}
	} else if ((pxmitframe->frame_tag & 0x0f) == TXAGG_FRAMETAG)
		RTW_INFO("pxmitframe->frame_tag == TXAGG_FRAMETAG\n");
	else if (((pxmitframe->frame_tag & 0x0f) == MP_FRAMETAG) &&
		 (padapter->registrypriv.mp_mode == 1))
		fill_txdesc_for_mp(padapter, ptxdesc);
	else {
		RTW_INFO("pxmitframe->frame_tag = %d\n",
			 pxmitframe->frame_tag);

		SET_TX_DESC_USE_RATE_8821C(ptxdesc, 1);
		DriverFixedRate = 0x01;
		SET_TX_DESC_DATARATE_8821C(ptxdesc,
					   MRateToHwRate(pmlmeext->tx_rate));
	}


	/*rtl8821c_fill_txdesc_bf(pxmitframe, ptxdesc);Todo for 8821C*/

	/*SET_TX_DESC_TX_BUFFER_SIZE_8812(ptxdesc, sz);*/

	if (DriverFixedRate)
		SWDefineContent |= 0x01;

	SET_TX_DESC_SW_DEFINE_8821C(ptxdesc, SWDefineContent);

	SET_TX_DESC_PORT_ID_8821C(ptxdesc, get_hw_port(padapter));
	SET_TX_DESC_MULTIPLE_PORT_8821C(ptxdesc, get_hw_port(padapter));

	rtl8821c_cal_txdesc_chksum(padapter, ptxdesc);
	rtl8821c_dbg_dump_tx_desc(padapter, pxmitframe->frame_tag, ptxdesc);
	return 0;
}

s32 rtl8821ce_dump_xframe(_adapter *padapter, struct xmit_frame *pxmitframe)
{
	s32 ret = _SUCCESS;
	s32 inner_ret = _SUCCESS;
	_irqL irqL;
	int t, sz, w_sz;
	u32 ff_hwaddr;
	struct xmit_buf *pxmitbuf = pxmitframe->pxmitbuf;
	struct pkt_attrib *pattrib = &pxmitframe->attrib;
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);
	struct security_priv *psecuritypriv = &padapter->securitypriv;
	u8 *txbd;
	struct rtw_tx_ring *ptx_ring;

	if ((pxmitframe->frame_tag == DATA_FRAMETAG) &&
	    (pxmitframe->attrib.ether_type != 0x0806) &&
	    (pxmitframe->attrib.ether_type != 0x888e) &&
	    (pxmitframe->attrib.dhcp_pkt != 1))
		rtw_issue_addbareq_cmd(padapter, pxmitframe);

	for (t = 0; t < pattrib->nr_frags; t++) {

		if (inner_ret != _SUCCESS && ret == _SUCCESS)
			ret = _FAIL;

		if (t != (pattrib->nr_frags - 1)) {

			sz = pxmitpriv->frag_len - 4;

			if (!psecuritypriv->sw_encrypt)
				sz -= pattrib->icv_len;
		} else {
			/* no frag */
			sz = pattrib->last_txcmdsz;
		}

		ff_hwaddr = rtw_get_ff_hwaddr(pxmitframe);

		_enter_critical(&pdvobjpriv->irq_th_lock, &irqL);
		txbd = get_txbd(GET_PRIMARY_ADAPTER(padapter), ff_hwaddr);

		ptx_ring = &(GET_PRIMARY_ADAPTER(padapter)->xmitpriv.tx_ring[ff_hwaddr]);


		if (txbd == NULL) {
			_exit_critical(&pdvobjpriv->irq_th_lock, &irqL);
			rtw_sctx_done_err(&pxmitbuf->sctx,
					  RTW_SCTX_DONE_TX_DESC_NA);
			rtw_free_xmitbuf(pxmitpriv, pxmitbuf);
			RTW_INFO("##### Tx desc unavailable !#####\n");
			break;
		}

		if (pattrib->qsel != HALMAC_TXDESC_QSEL_H2C_CMD)
			update_txdesc(pxmitframe, sz);

		/* rtl8821ce_update_txbd() must be called after update_txdesc()
		 * It rely on rtl8821ce_update_txbd() to map it into non cache memory
		 */

		rtl8821ce_update_txbd(pxmitframe, txbd, sz);

		if (pxmitbuf->buf_tag != XMITBUF_CMD)
			rtl8821ce_enqueue_xmitbuf(ptx_ring, pxmitbuf);

		pxmitbuf->len = sz + TX_WIFI_INFO_SIZE;
		w_sz = sz;

		/* Please comment here */
		wmb();
		fill_txbd_own(padapter, txbd, ff_hwaddr, ptx_ring);

		_exit_critical(&pdvobjpriv->irq_th_lock, &irqL);

		inner_ret = rtw_write_port(padapter, ff_hwaddr, w_sz,
					   (unsigned char *)pxmitbuf);

		rtw_count_tx_stats(padapter, pxmitframe, sz);
	}

	rtw_free_xmitframe(pxmitpriv, pxmitframe);

	if (ret != _SUCCESS)
		rtw_sctx_done_err(&pxmitbuf->sctx, RTW_SCTX_DONE_UNKNOWN);

	return ret;
}

/*
 * Packet should not be dequeued if there is no available descriptor
 * return: _SUCCESS if there is available descriptor
 */
static u8 check_tx_desc_resource(_adapter *padapter, int prio)
{
	struct xmit_priv	*pxmitpriv = &padapter->xmitpriv;
	struct rtw_tx_ring	*ring;

	ring = &pxmitpriv->tx_ring[prio];

	/*
	 * for now we reserve two free descriptor as a safety boundary
	 * between the tail and the head
	 */

	if ((ring->entries - ring->qlen) >= 2)
		return _TRUE;
	else
		return _FALSE;
}

static u8 check_nic_enough_desc_all(_adapter *padapter)
{
	u8 status = (check_tx_desc_resource(padapter, VI_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, VO_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, BE_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, BK_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, MGT_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, TXCMD_QUEUE_INX) &&
		     check_tx_desc_resource(padapter, HIGH_QUEUE_INX));
	return status;
}

static s32 xmitframe_direct(_adapter *padapter, struct xmit_frame *pxmitframe)
{
	s32 res = _SUCCESS;

	res = rtw_xmitframe_coalesce(padapter, pxmitframe->pkt, pxmitframe);
	if (res == _SUCCESS)
		rtl8821ce_dump_xframe(padapter, pxmitframe);

	return res;
}


void rtl8821ce_xmitframe_resume(_adapter *padapter)
{
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct xmit_frame *pxmitframe = NULL;
	struct xmit_buf	*pxmitbuf = NULL;
	int res = _SUCCESS, xcnt = 0;


	while (1) {
		if (RTW_CANNOT_RUN(padapter)) {
			RTW_INFO("%s => bDriverStopped or bSurpriseRemoved\n",
				 __func__);
			break;
		}

		if (!check_nic_enough_desc_all(padapter))
			break;

		pxmitbuf = rtw_alloc_xmitbuf(pxmitpriv);
		if (!pxmitbuf)
			break;


		pxmitframe =  rtw_dequeue_xframe(pxmitpriv, pxmitpriv->hwxmits,
						 pxmitpriv->hwxmit_entry);

		if (pxmitframe) {
			pxmitframe->pxmitbuf = pxmitbuf;
			pxmitframe->buf_addr = pxmitbuf->pbuf;
			pxmitbuf->priv_data = pxmitframe;

			if ((pxmitframe->frame_tag & 0x0f) == DATA_FRAMETAG) {
				if (pxmitframe->attrib.priority <= 15) {
					/* TID0~15 */
					res = rtw_xmitframe_coalesce(padapter,
						pxmitframe->pkt, pxmitframe);
				}

				/* always return ndis_packet after
				 * rtw_xmitframe_coalesce
				*/
				rtw_os_xmit_complete(padapter, pxmitframe);
			}

			if (res == _SUCCESS)
				rtl8821ce_dump_xframe(padapter, pxmitframe);
			else {
				rtw_free_xmitbuf(pxmitpriv, pxmitbuf);
				rtw_free_xmitframe(pxmitpriv, pxmitframe);
			}

			xcnt++;
		} else {
			rtw_free_xmitbuf(pxmitpriv, pxmitbuf);
			break;
		}
	}
}

static u8 check_nic_enough_desc(_adapter *padapter, struct pkt_attrib *pattrib)
{
	u32 prio;
	struct xmit_priv	*pxmitpriv = &padapter->xmitpriv;
	struct rtw_tx_ring	*ring;

	switch (pattrib->qsel) {
	case 0:
	case 3:
		prio = BE_QUEUE_INX;
		break;
	case 1:
	case 2:
		prio = BK_QUEUE_INX;
		break;
	case 4:
	case 5:
		prio = VI_QUEUE_INX;
		break;
	case 6:
	case 7:
		prio = VO_QUEUE_INX;
		break;
	default:
		prio = BE_QUEUE_INX;
		break;
	}

	ring = &pxmitpriv->tx_ring[prio];

	/*
	 * for now we reserve two free descriptor as a safety boundary
	 * between the tail and the head
	 */
	if ((ring->entries - ring->qlen) >= 2)
		return _TRUE;
	else
		return _FALSE;
}

/*
 * Return
 *	_TRUE	dump packet directly
 *	_FALSE	enqueue packet
 */
static s32 pre_xmitframe(_adapter *padapter, struct xmit_frame *pxmitframe)
{
	_irqL irqL;
	s32 res;
	struct xmit_buf *pxmitbuf = NULL;
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct pkt_attrib *pattrib = &pxmitframe->attrib;

	_enter_critical_bh(&pxmitpriv->lock, &irqL);

	if ((rtw_txframes_sta_ac_pending(padapter, pattrib) > 0) ||
	    (check_nic_enough_desc(padapter, pattrib) == _FALSE))
		goto enqueue;

	if (rtw_xmit_ac_blocked(padapter) == _TRUE)
		goto enqueue;

	if (DEV_STA_LG_NUM(padapter->dvobj))
		goto enqueue;


	pxmitbuf = rtw_alloc_xmitbuf(pxmitpriv);
	if (pxmitbuf == NULL)
		goto enqueue;

	_exit_critical_bh(&pxmitpriv->lock, &irqL);

	pxmitframe->pxmitbuf = pxmitbuf;
	pxmitframe->buf_addr = pxmitbuf->pbuf;
	pxmitbuf->priv_data = pxmitframe;

	if (xmitframe_direct(padapter, pxmitframe) != _SUCCESS) {
		rtw_free_xmitbuf(pxmitpriv, pxmitbuf);
		rtw_free_xmitframe(pxmitpriv, pxmitframe);
	}

	return _TRUE;

enqueue:
	res = rtw_xmitframe_enqueue(padapter, pxmitframe);

	_exit_critical_bh(&pxmitpriv->lock, &irqL);

	if (res != _SUCCESS) {
		rtw_free_xmitframe(pxmitpriv, pxmitframe);

		pxmitpriv->tx_drop++;
		return _TRUE;
	}

	return _FALSE;
}

s32 rtl8821ce_mgnt_xmit(_adapter *padapter, struct xmit_frame *pmgntframe)
{
	return rtl8821ce_dump_xframe(padapter, pmgntframe);
}

/*
 * Return
 *	_TRUE	dump packet directly ok
 *	_FALSE	temporary can't transmit packets to hardware
 */
s32 rtl8821ce_hal_xmit(_adapter *padapter, struct xmit_frame *pxmitframe)
{
	return pre_xmitframe(padapter, pxmitframe);
}

s32 rtl8821ce_hal_xmitframe_enqueue(_adapter *padapter,
				    struct xmit_frame *pxmitframe)
{
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	s32 err;

	err = rtw_xmitframe_enqueue(padapter, pxmitframe);
	if (err != _SUCCESS) {
		rtw_free_xmitframe(pxmitpriv, pxmitframe);
		pxmitpriv->tx_drop++;
	} else {
		if (check_nic_enough_desc(padapter,
					  &pxmitframe->attrib) == _TRUE)
			tasklet_hi_schedule(&pxmitpriv->xmit_tasklet);
	}

	return err;
}

int rtl8821ce_init_txbd_ring(_adapter *padapter, unsigned int q_idx,
			     unsigned int entries)
{
	struct xmit_priv *t_priv = &padapter->xmitpriv;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);
	struct pci_dev *pdev = pdvobjpriv->ppcidev;
	struct tx_buf_desc *txbd;
	dma_addr_t dma;

	RTW_INFO("%s entries num:%d\n", __func__, entries);

	txbd = pci_alloc_consistent(pdev, sizeof(*txbd) * entries, &dma);

	if (!txbd || (unsigned long)txbd & 0xFF) {
		RTW_INFO("Cannot allocate TXBD (q_idx = %d)\n", q_idx);
		return _FAIL;
	}

	_rtw_memset(txbd, 0, sizeof(*txbd) * entries);
	t_priv->tx_ring[q_idx].buf_desc = txbd;
	t_priv->tx_ring[q_idx].dma = dma;
	t_priv->tx_ring[q_idx].idx = 0;
	t_priv->tx_ring[q_idx].entries = entries;
	_rtw_init_queue(&t_priv->tx_ring[q_idx].queue);
	t_priv->tx_ring[q_idx].qlen = 0;

	RTW_INFO("%s queue:%d, ring_addr:%p\n", __func__, q_idx, txbd);

	return _SUCCESS;
}

void rtl8821ce_free_txbd_ring(_adapter *padapter, unsigned int prio)
{
	struct xmit_priv *t_priv = &padapter->xmitpriv;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(padapter);
	struct pci_dev *pdev = pdvobjpriv->ppcidev;
	struct rtw_tx_ring *ring = &t_priv->tx_ring[prio];
	u8 *txbd;
	struct xmit_buf	*pxmitbuf;

	while (ring->qlen) {
		txbd = (u8 *)(&ring->buf_desc[ring->idx]);
		SET_TX_BD_OWN(txbd, 0);

		if (prio != BCN_QUEUE_INX)
			ring->idx = (ring->idx + 1) % ring->entries;

		pxmitbuf = rtl8821ce_dequeue_xmitbuf(ring);

		if (pxmitbuf) {
			pci_unmap_single(pdev,
				GET_TX_BD_PHYSICAL_ADDR0_LOW(txbd),
				pxmitbuf->len, PCI_DMA_TODEVICE);

			rtw_free_xmitbuf(t_priv, pxmitbuf);

		} else {
			RTW_INFO("%s qlen=%d!=0,but have xmitbuf in pendingQ\n",
				 __func__, ring->qlen);
			break;
		}
	}

	pci_free_consistent(pdev, sizeof(*ring->buf_desc) * ring->entries,
			    ring->buf_desc, ring->dma);
	ring->buf_desc = NULL;

}

/*
 * Draw a line to show queue status. For debug
 * i: queue index / W:HW index / h:host index / .: enpty entry / *:ready to DMA
 * Example:  R- 3- 4- 8 ..iW***h..... (i=3,W=4,h=8,
 * *** means 3 tx_desc is reaady to dma)
 */

/*
 * Read pointer is h/w descriptor index
 * Write pointer is host desciptor index: For tx side, if own bit is set in
 * packet index n, host pointer (write pointer) point to index n + 1.
 */
static u32 rtl8821ce_check_txdesc_closed(PADAPTER Adapter, u32 queue_idx,
		struct rtw_tx_ring *ring)
{
	/*
	 * hw_rp_cache is used to reduce REG access.
	 */
	u32	tmp32;

	/* bcn queue should not enter this function */
	if (queue_idx == BCN_QUEUE_INX)
		return _TRUE;

	/* qlen == 0 --> don't need to process */
	if (ring->qlen == 0)
		return _FALSE;

	/* sw_rp == hw_rp_cache --> sync hw_rp */
	if (ring->idx == ring->hw_rp_cache) {

		tmp32 = rtw_read32(Adapter, get_txbd_rw_reg(queue_idx));

		ring->hw_rp_cache = (tmp32 >> 16) & 0x0FFF;
	}

	/* check if need to handle TXOK */
	if (ring->idx == ring->hw_rp_cache)
		return _FALSE;

	return _TRUE;
}

void rtl8821ce_tx_isr(PADAPTER Adapter, int prio)
{
	struct xmit_priv *t_priv = &Adapter->xmitpriv;
	struct dvobj_priv *pdvobjpriv = adapter_to_dvobj(Adapter);
	struct rtw_tx_ring *ring = &t_priv->tx_ring[prio];
	struct xmit_buf	*pxmitbuf;
	u8 *tx_desc;

	while (ring->qlen) {
		tx_desc = (u8 *)&ring->buf_desc[ring->idx];

		/*  beacon use cmd buf Never run into here */
		if (!rtl8821ce_check_txdesc_closed(Adapter, prio, ring))
			return;

		buf_desc_debug("TX: %s, q_idx = %d, tx_bd = %04x, close [%04x] r_idx [%04x]\n",
			       __func__, prio, (u32)tx_desc, ring->idx,
			       (ring->idx + 1) % ring->entries);

		ring->idx = (ring->idx + 1) % ring->entries;
		pxmitbuf = rtl8821ce_dequeue_xmitbuf(ring);

		if (pxmitbuf) {
			pci_unmap_single(pdvobjpriv->ppcidev,
				GET_TX_BD_PHYSICAL_ADDR0_LOW(tx_desc),
				pxmitbuf->len, PCI_DMA_TODEVICE);
			rtw_sctx_done(&pxmitbuf->sctx);
			rtw_free_xmitbuf(&(pxmitbuf->padapter->xmitpriv),
					 pxmitbuf);
		} else {
			RTW_INFO("%s qlen=%d!=0,but have xmitbuf in pendingQ\n",
				 __func__, ring->qlen);
		}
	}

	if (check_tx_desc_resource(Adapter, prio)
	    && rtw_xmit_ac_blocked(Adapter) != _TRUE)
		rtw_mi_xmit_tasklet_schedule(Adapter);
}


