/******************************************************************************
 *
 * Copyright(c) 2007 - 2012 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.
 *
 ******************************************************************************/
#define _USB_OPS_LINUX_C_

#include <drv_types.h>
#include <usb_ops_linux.h>
#include <rtw_sreset.h>

void rtl8723au_read_port_cancel(struct rtw_adapter *padapter)
{
	struct recv_buf *precvbuf;
	int i;

	precvbuf = (struct recv_buf *)padapter->recvpriv.precv_buf;

	DBG_8723A("%s\n", __func__);

	padapter->bReadPortCancel = true;

	for (i = 0; i < NR_RECVBUFF ; i++) {
		if (precvbuf->purb)
			usb_kill_urb(precvbuf->purb);
		precvbuf++;
	}
	usb_kill_urb(padapter->recvpriv.int_in_urb);
}

static void usb_write_port23a_complete(struct urb *purb)
{
	struct xmit_buf *pxmitbuf = (struct xmit_buf *)purb->context;
	struct rtw_adapter *padapter = pxmitbuf->padapter;
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct hal_data_8723a *phaldata;
	unsigned long irqL;

	switch (pxmitbuf->flags) {
	case HIGH_QUEUE_INX:
#ifdef CONFIG_8723AU_AP_MODE
		rtw_chk_hi_queue_cmd23a(padapter);
#endif
		break;
	default:
		break;
	}

	if (padapter->bSurpriseRemoved || padapter->bDriverStopped ||
	    padapter->bWritePortCancel) {
		RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
			 "usb_write_port23a_complete:bDriverStopped(%d) OR bSurpriseRemoved(%d)\n",
			 padapter->bDriverStopped, padapter->bSurpriseRemoved);
		DBG_8723A("%s(): TX Warning! bDriverStopped(%d) OR "
			  "bSurpriseRemoved(%d) bWritePortCancel(%d) "
			  "pxmitbuf->ext_tag(%x)\n", __func__,
			  padapter->bDriverStopped, padapter->bSurpriseRemoved,
			  padapter->bReadPortCancel, pxmitbuf->ext_tag);

		goto check_completion;
	}

	if (purb->status) {
		RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
			 "usb_write_port23a_complete : purb->status(%d) != 0\n",
			 purb->status);
		DBG_8723A("###=> urb_write_port_complete status(%d)\n",
			  purb->status);
		if (purb->status == -EPIPE || purb->status == -EPROTO) {
		} else if (purb->status == -EINPROGRESS) {
			RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
				 "usb_write_port23a_complete: EINPROGESS\n");
			goto check_completion;
		} else if (purb->status == -ENOENT) {
			DBG_8723A("%s: -ENOENT\n", __func__);
			goto check_completion;
		} else if (purb->status == -ECONNRESET) {
			DBG_8723A("%s: -ECONNRESET\n", __func__);
			goto check_completion;
		} else if (purb->status == -ESHUTDOWN) {
			RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
				 "usb_write_port23a_complete: ESHUTDOWN\n");
			padapter->bDriverStopped = true;
			RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
				 "usb_write_port23a_complete:bDriverStopped = true\n");
			goto check_completion;
		} else {
			padapter->bSurpriseRemoved = true;
			DBG_8723A("bSurpriseRemoved = true\n");
			RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
				 "usb_write_port23a_complete:bSurpriseRemoved = true\n");
			goto check_completion;
		}
	}
	phaldata = GET_HAL_DATA(padapter);
	phaldata->srestpriv.last_tx_complete_time = jiffies;

check_completion:
	spin_lock_irqsave(&pxmitpriv->lock_sctx, irqL);
	rtw23a_sctx_done_err(&pxmitbuf->sctx,
			     purb->status ? RTW_SCTX_DONE_WRITE_PORT_ERR :
			     RTW_SCTX_DONE_SUCCESS);
	spin_unlock_irqrestore(&pxmitpriv->lock_sctx, irqL);

	rtw_free_xmitbuf23a(pxmitpriv, pxmitbuf);

	tasklet_hi_schedule(&pxmitpriv->xmit_tasklet);
}

int rtl8723au_write_port(struct rtw_adapter *padapter, u32 addr, u32 cnt,
			 struct xmit_buf *pxmitbuf)
{
	struct urb *purb = NULL;
	struct dvobj_priv *pdvobj = adapter_to_dvobj(padapter);
	struct xmit_priv *pxmitpriv = &padapter->xmitpriv;
	struct xmit_frame *pxmitframe;
	struct usb_device *pusbd = pdvobj->pusbdev;
	unsigned long irqL;
	unsigned int pipe, ep_num;
	int status;
	int ret = _FAIL;

	RT_TRACE(_module_hci_ops_os_c_, _drv_err_, "+usb_write_port23a\n");

	if (padapter->bDriverStopped || padapter->bSurpriseRemoved) {
		RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
			 "%s:(padapter->bDriverStopped || padapter->bSurpriseRemoved)!!!\n",
			 __func__);
		rtw23a_sctx_done_err(&pxmitbuf->sctx, RTW_SCTX_DONE_TX_DENY);
		goto exit;
	}

	pxmitframe = (struct xmit_frame *)pxmitbuf->priv_data;
	spin_lock_irqsave(&pxmitpriv->lock, irqL);

	switch (addr) {
	case VO_QUEUE_INX:
		pxmitbuf->flags = VO_QUEUE_INX;
		break;
	case VI_QUEUE_INX:
		pxmitbuf->flags = VI_QUEUE_INX;
		break;
	case BE_QUEUE_INX:
		pxmitbuf->flags = BE_QUEUE_INX;
		break;
	case BK_QUEUE_INX:
		pxmitbuf->flags = BK_QUEUE_INX;
		break;
	case HIGH_QUEUE_INX:
		pxmitbuf->flags = HIGH_QUEUE_INX;
		break;
	default:
		pxmitbuf->flags = MGT_QUEUE_INX;
		break;
	}

	spin_unlock_irqrestore(&pxmitpriv->lock, irqL);

	purb = pxmitbuf->pxmit_urb[0];

	/* translate DMA FIFO addr to pipehandle */
	ep_num = pdvobj->Queue2Pipe[addr];
	pipe = usb_sndbulkpipe(pusbd, ep_num);

	usb_fill_bulk_urb(purb, pusbd, pipe,
			  pxmitframe->buf_addr, /*  pxmitbuf->pbuf */
			  cnt, usb_write_port23a_complete,
			  pxmitbuf);/* context is pxmitbuf */

	status = usb_submit_urb(purb, GFP_ATOMIC);
	if (!status) {
		struct hal_data_8723a *phaldata = GET_HAL_DATA(padapter);
		phaldata->srestpriv.last_tx_time = jiffies;
	} else {
		rtw23a_sctx_done_err(&pxmitbuf->sctx,
				     RTW_SCTX_DONE_WRITE_PORT_ERR);
		DBG_8723A("usb_write_port23a, status =%d\n", status);
		RT_TRACE(_module_hci_ops_os_c_, _drv_err_,
			 "usb_write_port23a(): usb_submit_urb, status =%x\n",
			 status);

		switch (status) {
		case -ENODEV:
			padapter->bDriverStopped = true;
			break;
		default:
			break;
		}
		goto exit;
	}
	ret = _SUCCESS;
	RT_TRACE(_module_hci_ops_os_c_, _drv_err_, "-usb_write_port23a\n");

exit:
	if (ret != _SUCCESS)
		rtw_free_xmitbuf23a(pxmitpriv, pxmitbuf);

	return ret;
}

void rtl8723au_write_port_cancel(struct rtw_adapter *padapter)
{
	struct xmit_buf *pxmitbuf;
	struct list_head *plist;
	int j;

	DBG_8723A("%s\n", __func__);

	padapter->bWritePortCancel = true;

	list_for_each(plist, &padapter->xmitpriv.xmitbuf_list) {
		pxmitbuf = container_of(plist, struct xmit_buf, list2);
		for (j = 0; j < 8; j++) {
			if (pxmitbuf->pxmit_urb[j])
				usb_kill_urb(pxmitbuf->pxmit_urb[j]);
		}
	}
	list_for_each(plist, &padapter->xmitpriv.xmitextbuf_list) {
		pxmitbuf = container_of(plist, struct xmit_buf, list2);
		for (j = 0; j < 8; j++) {
			if (pxmitbuf->pxmit_urb[j])
				usb_kill_urb(pxmitbuf->pxmit_urb[j]);
		}
	}
}
