/******************************************************************************
 * Copyright(c) 2008 - 2010 Realtek Corporation. All rights reserved.
 *
 * 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
 *
 * The full GNU General Public License is included in this distribution in the
 * file called LICENSE.
 *
 * Contact Information:
 * wlanfae <wlanfae@realtek.com>
******************************************************************************/

#include "rtl_core.h"
#include "r8192E_hw.h"
#include "r8192E_cmdpkt.h"

bool cmpk_message_handle_tx(
	struct net_device *dev,
	u8	*code_virtual_address,
	u32	packettype,
	u32	buffer_len)
{

	bool				rt_status = true;
	struct r8192_priv *priv = rtllib_priv(dev);
	u16				frag_threshold;
	u16				frag_length = 0, frag_offset = 0;
	struct rt_firmware *pfirmware = priv->pFirmware;
	struct sk_buff		*skb;
	unsigned char		*seg_ptr;
	struct cb_desc *tcb_desc;
	u8				bLastIniPkt;

	struct tx_fwinfo_8190pci *pTxFwInfo = NULL;

	RT_TRACE(COMP_CMDPKT, "%s(),buffer_len is %d\n", __func__, buffer_len);
	firmware_init_param(dev);
	frag_threshold = pfirmware->cmdpacket_frag_thresold;

	do {
		if ((buffer_len - frag_offset) > frag_threshold) {
			frag_length = frag_threshold ;
			bLastIniPkt = 0;

		} else {
			frag_length = (u16)(buffer_len - frag_offset);
			bLastIniPkt = 1;
		}

		skb  = dev_alloc_skb(frag_length +
				     priv->rtllib->tx_headroom + 4);

		if (skb == NULL) {
			rt_status = false;
			goto Failed;
		}

		memcpy((unsigned char *)(skb->cb), &dev, sizeof(dev));
		tcb_desc = (struct cb_desc *)(skb->cb + MAX_DEV_ADDR_SIZE);
		tcb_desc->queue_index = TXCMD_QUEUE;
		tcb_desc->bCmdOrInit = DESC_PACKET_TYPE_NORMAL;
		tcb_desc->bLastIniPkt = bLastIniPkt;
		tcb_desc->pkt_size = frag_length;

		seg_ptr = skb_put(skb, priv->rtllib->tx_headroom);
		pTxFwInfo = (struct tx_fwinfo_8190pci *)seg_ptr;
		memset(pTxFwInfo, 0, sizeof(struct tx_fwinfo_8190pci));
		memset(pTxFwInfo, 0x12, 8);

		seg_ptr = skb_put(skb, frag_length);
		memcpy(seg_ptr, code_virtual_address, (u32)frag_length);

		priv->rtllib->softmac_hard_start_xmit(skb, dev);

		code_virtual_address += frag_length;
		frag_offset += frag_length;

	} while (frag_offset < buffer_len);

	write_nic_byte(dev, TPPoll, TPPoll_CQ);
Failed:
	return rt_status;
}

static	void
cmpk_count_txstatistic(
	struct net_device *dev,
	struct cmpk_txfb *pstx_fb)
{
	struct r8192_priv *priv = rtllib_priv(dev);
#ifdef ENABLE_PS
	enum rt_rf_power_state rtState;

	pAdapter->HalFunc.GetHwRegHandler(pAdapter, HW_VAR_RF_STATE,
					  (pu1Byte)(&rtState));

	if (rtState == eRfOff)
		return;
#endif

	if (pstx_fb->tok) {
		priv->stats.txfeedbackok++;
		priv->stats.txoktotal++;
		priv->stats.txokbytestotal += pstx_fb->pkt_length;
		priv->stats.txokinperiod++;

		if (pstx_fb->pkt_type == PACKET_MULTICAST) {
			priv->stats.txmulticast++;
			priv->stats.txbytesmulticast += pstx_fb->pkt_length;
		} else if (pstx_fb->pkt_type == PACKET_BROADCAST) {
			priv->stats.txbroadcast++;
			priv->stats.txbytesbroadcast += pstx_fb->pkt_length;
		} else {
			priv->stats.txunicast++;
			priv->stats.txbytesunicast += pstx_fb->pkt_length;
		}
	} else {
		priv->stats.txfeedbackfail++;
		priv->stats.txerrtotal++;
		priv->stats.txerrbytestotal += pstx_fb->pkt_length;

		if (pstx_fb->pkt_type == PACKET_MULTICAST)
			priv->stats.txerrmulticast++;
		else if (pstx_fb->pkt_type == PACKET_BROADCAST)
			priv->stats.txerrbroadcast++;
		else
			priv->stats.txerrunicast++;
	}

	priv->stats.txretrycount += pstx_fb->retry_cnt;
	priv->stats.txfeedbackretry += pstx_fb->retry_cnt;
}

static void cmpk_handle_tx_feedback(struct net_device *dev, u8 *pmsg)
{
	struct r8192_priv *priv = rtllib_priv(dev);
	struct cmpk_txfb rx_tx_fb;

	priv->stats.txfeedback++;


	memcpy((u8 *)&rx_tx_fb, pmsg, sizeof(struct cmpk_txfb));
	cmpk_count_txstatistic(dev, &rx_tx_fb);
}

static void cmdpkt_beacontimerinterrupt_819xusb(struct net_device *dev)
{
	struct r8192_priv *priv = rtllib_priv(dev);

	if ((priv->rtllib->current_network.mode == IEEE_A)  ||
	    (priv->rtllib->current_network.mode == IEEE_N_5G) ||
	    ((priv->rtllib->current_network.mode == IEEE_N_24G)  &&
	    (!priv->rtllib->pHTInfo->bCurSuppCCK)))
		DMESG("send beacon frame  tx rate is 6Mbpm\n");
	else
		DMESG("send beacon frame  tx rate is 1Mbpm\n");
}

static void cmpk_handle_interrupt_status(struct net_device *dev, u8 *pmsg)
{
	struct cmpk_intr_sta rx_intr_status;	/* */
	struct r8192_priv *priv = rtllib_priv(dev);

	DMESG("---> cmpk_Handle_Interrupt_Status()\n");

	rx_intr_status.length = pmsg[1];
	if (rx_intr_status.length != (sizeof(struct cmpk_intr_sta) - 2)) {
		DMESG("cmpk_Handle_Interrupt_Status: wrong length!\n");
		return;
	}

	if (priv->rtllib->iw_mode == IW_MODE_ADHOC) {
		rx_intr_status.interrupt_status = *((u32 *)(pmsg + 4));

		DMESG("interrupt status = 0x%x\n",
		      rx_intr_status.interrupt_status);

		if (rx_intr_status.interrupt_status & ISR_TxBcnOk) {
			priv->rtllib->bibsscoordinator = true;
			priv->stats.txbeaconokint++;
		} else if (rx_intr_status.interrupt_status & ISR_TxBcnErr) {
			priv->rtllib->bibsscoordinator = false;
			priv->stats.txbeaconerr++;
		}

		if (rx_intr_status.interrupt_status & ISR_BcnTimerIntr)
			cmdpkt_beacontimerinterrupt_819xusb(dev);
	}

	DMESG("<---- cmpk_handle_interrupt_status()\n");

}

static	void cmpk_handle_query_config_rx(struct net_device *dev, u8 *pmsg)
{
	cmpk_query_cfg_t	rx_query_cfg;


	rx_query_cfg.cfg_action = (pmsg[4] & 0x80000000)>>31;
	rx_query_cfg.cfg_type = (pmsg[4] & 0x60) >> 5;
	rx_query_cfg.cfg_size = (pmsg[4] & 0x18) >> 3;
	rx_query_cfg.cfg_page = (pmsg[6] & 0x0F) >> 0;
	rx_query_cfg.cfg_offset	 = pmsg[7];
	rx_query_cfg.value = (pmsg[8] << 24) | (pmsg[9] << 16) |
			     (pmsg[10] << 8) | (pmsg[11] << 0);
	rx_query_cfg.mask = (pmsg[12] << 24) | (pmsg[13] << 16) |
			    (pmsg[14] << 8) | (pmsg[15] << 0);

}

static void cmpk_count_tx_status(struct net_device *dev,
				 struct cmpk_tx_status *pstx_status)
{
	struct r8192_priv *priv = rtllib_priv(dev);

#ifdef ENABLE_PS

	enum rt_rf_power_state rtstate;

	pAdapter->HalFunc.GetHwRegHandler(pAdapter, HW_VAR_RF_STATE,
					  (pu1Byte)(&rtState));

	if (rtState == eRfOff)
		return;
#endif

	priv->stats.txfeedbackok	+= pstx_status->txok;
	priv->stats.txoktotal		+= pstx_status->txok;

	priv->stats.txfeedbackfail	+= pstx_status->txfail;
	priv->stats.txerrtotal		+= pstx_status->txfail;

	priv->stats.txretrycount		+= pstx_status->txretry;
	priv->stats.txfeedbackretry	+= pstx_status->txretry;


	priv->stats.txmulticast	+= pstx_status->txmcok;
	priv->stats.txbroadcast	+= pstx_status->txbcok;
	priv->stats.txunicast		+= pstx_status->txucok;

	priv->stats.txerrmulticast	+= pstx_status->txmcfail;
	priv->stats.txerrbroadcast	+= pstx_status->txbcfail;
	priv->stats.txerrunicast	+= pstx_status->txucfail;

	priv->stats.txbytesmulticast	+= pstx_status->txmclength;
	priv->stats.txbytesbroadcast	+= pstx_status->txbclength;
	priv->stats.txbytesunicast		+= pstx_status->txuclength;

	priv->stats.last_packet_rate		= pstx_status->rate;
}

static	void cmpk_handle_tx_status(struct net_device *dev, u8 *pmsg)
{
	struct cmpk_tx_status rx_tx_sts;

	memcpy((void *)&rx_tx_sts, (void *)pmsg, sizeof(struct cmpk_tx_status));
	cmpk_count_tx_status(dev, &rx_tx_sts);
}

static	void cmpk_handle_tx_rate_history(struct net_device *dev, u8 *pmsg)
{
	struct cmpk_tx_rahis *ptxrate;
	u8 i, j;
	u16				length = sizeof(struct cmpk_tx_rahis);
	u32 *ptemp;
	struct r8192_priv *priv = rtllib_priv(dev);

#ifdef ENABLE_PS
	pAdapter->HalFunc.GetHwRegHandler(pAdapter, HW_VAR_RF_STATE,
					 (pu1Byte)(&rtState));

	if (rtState == eRfOff)
		return;
#endif

	ptemp = (u32 *)pmsg;

	for (i = 0; i < (length / 4); i++) {
		u16	 temp1, temp2;

		temp1 = ptemp[i] & 0x0000FFFF;
		temp2 = ptemp[i] >> 16;
		ptemp[i] = (temp1 << 16) | temp2;
	}

	ptxrate = (struct cmpk_tx_rahis *)pmsg;

	if (ptxrate == NULL)
		return;

	for (i = 0; i < 16; i++) {
		if (i < 4)
			priv->stats.txrate.cck[i] += ptxrate->cck[i];

		if (i < 8)
			priv->stats.txrate.ofdm[i] += ptxrate->ofdm[i];

		for (j = 0; j < 4; j++)
			priv->stats.txrate.ht_mcs[j][i] +=
							 ptxrate->ht_mcs[j][i];
	}
}

u32 cmpk_message_handle_rx(struct net_device *dev,
			   struct rtllib_rx_stats *pstats)
{
	int			total_length;
	u8			cmd_length, exe_cnt = 0;
	u8			element_id;
	u8			*pcmd_buff;

	RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx()\n");

	if (pstats == NULL)
		return 0;

	total_length = pstats->Length;

	pcmd_buff = pstats->virtual_address;

	element_id = pcmd_buff[0];

	while (total_length > 0 || exe_cnt++ > 100) {
		element_id = pcmd_buff[0];

		switch (element_id) {
		case RX_TX_FEEDBACK:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "RX_TX_FEEDBACK\n");
			cmpk_handle_tx_feedback(dev, pcmd_buff);
			cmd_length = CMPK_RX_TX_FB_SIZE;
			break;
		case RX_INTERRUPT_STATUS:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "RX_INTERRUPT_STATUS\n");
			cmpk_handle_interrupt_status(dev, pcmd_buff);
			cmd_length = sizeof(struct cmpk_intr_sta);
			break;
		case BOTH_QUERY_CONFIG:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "BOTH_QUERY_CONFIG\n");
			cmpk_handle_query_config_rx(dev, pcmd_buff);
			cmd_length = CMPK_BOTH_QUERY_CONFIG_SIZE;
			break;
		case RX_TX_STATUS:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "RX_TX_STATUS\n");
			cmpk_handle_tx_status(dev, pcmd_buff);
			cmd_length = CMPK_RX_TX_STS_SIZE;
			break;
		case RX_TX_PER_PKT_FEEDBACK:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "RX_TX_PER_PKT_FEEDBACK\n");
			cmd_length = CMPK_RX_TX_FB_SIZE;
			break;
		case RX_TX_RATE_HISTORY:
			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "RX_TX_HISTORY\n");
			cmpk_handle_tx_rate_history(dev, pcmd_buff);
			cmd_length = CMPK_TX_RAHIS_SIZE;
			break;
		default:

			RT_TRACE(COMP_CMDPKT, "---->cmpk_message_handle_rx():"
				 "unknown CMD Element\n");
			return 1;
		}

		total_length -= cmd_length;
		pcmd_buff    += cmd_length;
	}
	return	1;
}
