/*
 * Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */
#include <adf_os_types.h>
#include <adf_os_dma.h>
#include <adf_os_timer.h>
#include <adf_os_time.h>
#include <adf_os_lock.h>
#include <adf_os_io.h>
#include <adf_os_mem.h>
#include <adf_os_module.h>
#include <adf_os_util.h>
#include <adf_os_stdtypes.h>
#include <adf_os_defer.h>
#include <adf_os_atomic.h>
#include <adf_nbuf.h>
#include "adf_net_types.h"
#include <athdefs.h>
#include "a_types.h"
#include "athdefs.h"
#include "a_osapi.h"
#include <linux/usb.h>
#include "hif_usb_internal.h"
#include <hif.h>
#include <htc_services.h>
#include <ol_if_athvar.h>
#include <if_usb.h>
#define ATH_MODULE_NAME hif
#include <a_debug.h>

#define USB_HIF_USE_SINGLE_PIPE_FOR_DATA
#define USB_HIF_TARGET_CREDIT_SIZE  1664
#ifdef WLAN_DEBUG
static ATH_DEBUG_MASK_DESCRIPTION g_HIFDebugDescription[] = {
	{USB_HIF_DEBUG_CTRL_TRANS, "Control Transfers"},
	{USB_HIF_DEBUG_BULK_IN, "BULK In Transfers"},
	{USB_HIF_DEBUG_BULK_OUT, "BULK Out Transfers"},
	{USB_HIF_DEBUG_DUMP_DATA, "Dump data"},
	{USB_HIF_DEBUG_ENUM, "Enumeration"},
};

ATH_DEBUG_INSTANTIATE_MODULE_VAR(hif,
				 "hif",
				 "USB Host Interface",
				 ATH_DEBUG_MASK_DEFAULTS | ATH_DEBUG_INFO |
				 USB_HIF_DEBUG_ENUM,
				 ATH_DEBUG_DESCRIPTION_COUNT
				 (g_HIFDebugDescription),
				 g_HIFDebugDescription);

#endif

/* use credit flow control over HTC */
unsigned int htc_credit_flow;
module_param(htc_credit_flow, uint, 0644);

#ifdef USB_ISOC_SUPPORT
unsigned int hif_usb_isoch_vo = 1;
#else
unsigned int hif_usb_isoch_vo;
#endif
module_param(hif_usb_isoch_vo, uint, 0644);
unsigned int hif_usb_disable_rxdata2 = 1;
module_param(hif_usb_disable_rxdata2, uint, 0644);

unsigned int hif_usbaudioclass;
module_param(hif_usbaudioclass, uint, 0644);

static void usb_hif_destroy(HIF_DEVICE_USB *device);
static HIF_DEVICE_USB *usb_hif_create(struct usb_interface *interface);

OSDRV_CALLBACKS osDrvcallback;

int notifyDeviceInsertedHandler(void *hHIF)
{
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	osDrvcallback.deviceInsertedHandler(osDrvcallback.context, hHIF);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	complete_and_exit(NULL, 0);
	return 0;
}

int notifyDeviceSurprisedRemovedHandler(void *context)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) context;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	osDrvcallback.deviceRemovedHandler(device->claimed_context, device);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	complete_and_exit(NULL, 0);
	return 0;
}

int HIF_USBDeviceInserted(struct usb_interface *interface, hif_handle_t hif_hdl)
{
	HIF_DEVICE_USB *device = NULL;
	int retval = -1;
	struct hif_usb_softc *sc = hif_hdl;
	struct usb_device *udev = interface_to_usbdev(interface);

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	AR_DEBUG_PRINTF(ATH_DEBUG_INFO, ("BCDDevice : %x\n",
					 udev->descriptor.bcdDevice));

	do {

		device = usb_hif_create(interface);
		if (NULL == device) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("device is NULL\n"));
			break;
		}
		device->sc = sc;
		sc->hif_device = (HIF_DEVICE *) device;
		retval = 0;

	} while (FALSE);

	if ((device != NULL) && (retval < 0)) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("abnormal condition\n"));
		usb_hif_destroy(device);
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	return retval;
}

void HIF_USBDeviceDetached(struct usb_interface *interface,
			   a_uint8_t surprise_removed)
{
	HIF_DEVICE_USB *device;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	do {

		device = (HIF_DEVICE_USB *) usb_get_intfdata(interface);
		if (NULL == device) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("device is NULL\n"));
			break;
		}

		device->surpriseRemoved = surprise_removed;
		/* inform upper layer if it is still interested */
		if (surprise_removed
		    && (osDrvcallback.deviceRemovedHandler != NULL)
		    && (device->claimed_context != NULL)) {
			osDrvcallback.deviceRemovedHandler(device->
							   claimed_context,
							   device);
		}

		usb_hif_destroy(device);
		athdiag_procfs_remove();
	} while (FALSE);

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));

}

static void usb_hif_usb_transmit_complete(struct urb *urb)
{
	HIF_URB_CONTEXT *urb_context = (HIF_URB_CONTEXT *) urb->context;
	adf_nbuf_t buf;
	HIF_USB_PIPE *pipe = urb_context->pipe;
	struct HIFSendContext *pSendContext;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT,
			("+%s: pipe: %d, stat:%d, len:%d\n", __func__,
			 pipe->logical_pipe_num, urb->status,
			 urb->actual_length));

	/* this urb is not pending anymore */
	usb_hif_remove_pending_transfer(urb_context);

	if (urb->status != 0) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("%s:  pipe: %d, failed:%d\n",
						__func__,
						pipe->logical_pipe_num,
						urb->status));
	}

	a_mem_trace(urb_context->buf);
	buf = urb_context->buf;
	pSendContext = urb_context->pSendContext;

	if (pSendContext->bNewAlloc) {
		adf_os_mem_free((void *)pSendContext);
	} else {
		adf_nbuf_pull_head(buf, pSendContext->head_data_len);
	}

	urb_context->buf = NULL;
	usb_hif_cleanup_transmit_urb(urb_context);

#if 0
	if (pipe->urbcnt >= pipe->urb_cnt_thresh)
		/* TBD */
#endif

	/* note: queue implements a lock */
	skb_queue_tail(&pipe->io_comp_queue, buf);
#ifdef HIF_USB_TASKLET
	tasklet_schedule(&pipe->io_complete_tasklet);
#else
	schedule_work(&pipe->io_complete_work);
#endif

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT, ("-%s\n", __func__));
}

static A_STATUS HIFSend_internal(HIF_DEVICE *hifDevice, a_uint8_t PipeID,
		 adf_nbuf_t hdr_buf, adf_nbuf_t buf, unsigned int nbytes)
{
	A_STATUS status = A_OK;
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;
	HIF_USB_PIPE *pipe = &device->pipes[PipeID];
	HIF_URB_CONTEXT *urb_context;
	A_UINT8 *data;
	A_UINT32 len;
	struct urb *urb;
	int usb_status;
	int i;
	struct HIFSendContext *pSendContext;
	uint8_t frag_count;
	uint32_t head_data_len, tmp_frag_count = 0;
	unsigned char *pData;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT, ("+%s pipe : %d, buf:0x%pK\n",
			__func__, PipeID, buf));

	a_mem_trace(buf);

	frag_count = adf_nbuf_get_num_frags(buf);
	if (frag_count == 1) {
		/*
		 * | HIFSendContext | netbuf->data
		 */
		head_data_len = sizeof(struct HIFSendContext);
	} else if ((frag_count - 1) <= CVG_NBUF_MAX_EXTRA_FRAGS) {
		/*
		 * means have extra fragment buf in skb
		 * header data length should be total sending length substract
		 * internal data length of netbuf
		 * | HIFSendContext | fragments except internal buffer |
		 * netbuf->data
		 */
		head_data_len = sizeof(struct HIFSendContext);
		while (tmp_frag_count < (frag_count - 1)) {
			head_data_len =
				head_data_len + adf_nbuf_get_frag_len(buf,
						tmp_frag_count);
			tmp_frag_count = tmp_frag_count + 1;
		}
	} else {
		/* Extra fragments overflow */
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
			"%s Extra fragments count overflow : %d\n",
			__func__, frag_count));
		status = A_ERROR;
		goto exit;
	}

	/* Check whether head room is enough to save extra head data */
	if (head_data_len <= adf_nbuf_headroom(buf)) {
		pSendContext = (struct HIFSendContext *)adf_nbuf_push_head(buf,
				head_data_len);
		pSendContext->bNewAlloc = FALSE;
	} else {
		pSendContext = adf_os_mem_alloc(NULL,
				sizeof
				(struct
				 HIFSendContext)
				+
				head_data_len
				+
				nbytes);
		pSendContext->bNewAlloc = TRUE;
	}
	pSendContext->netbuf = buf;
	pSendContext->pDev = hifDevice;
	pSendContext->transferID = PipeID;
	pSendContext->head_data_len = head_data_len;
	/*
	 * Copy data to head part of netbuf or head of allocated buffer.
	 * if buffer is new allocated, the last buffer should be copied also.
	 * It assume last fragment is internal buffer of netbuf
	 * sometime total length of fragments larger than nbytes
	 */
	pData = (unsigned char *)pSendContext + sizeof(struct HIFSendContext);
	for (i = 0; i < (pSendContext->bNewAlloc ? frag_count : frag_count - 1);
			i++) {
		int frag_len = adf_nbuf_get_frag_len(buf, i);
		unsigned char *frag_addr = adf_nbuf_get_frag_vaddr(buf, i);
		memcpy(pData, frag_addr, frag_len);
		pData += frag_len;
	}
	/* Reset pData pointer and send out */
	pData = (unsigned char *)pSendContext + sizeof(struct HIFSendContext);

	do {
		urb_context = usb_hif_alloc_urb_from_pipe(pipe);
		if (NULL == urb_context) {
			/* TODO : note, it is possible to run out of urbs if 2
			 * endpoints map to the same pipe ID
			 */
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
				"%s pipe:%d no urbs left. URB Cnt : %d\n",
				__func__, PipeID, pipe->urb_cnt));
			status = A_NO_RESOURCE;
			break;
		}
		urb_context->pSendContext = pSendContext;
		urb = urb_context->urb;
		urb_context->buf = buf;
		data = pData;
		len = nbytes;

		usb_fill_bulk_urb(urb,
				  device->udev,
				  pipe->usb_pipe_handle,
				  data,
				  (len % pipe->max_packet_size) ==
				  0 ? (len + 1) : len,
				  usb_hif_usb_transmit_complete, urb_context);

		if ((len % pipe->max_packet_size) == 0) {
			/* hit a max packet boundary on this pipe */
			/* urb->transfer_flags |= URB_ZERO_PACKET; */
		}

		AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT, (
			"athusb bulk send submit:%d, 0x%X (ep:0x%2.2X), %d bytes\n",
			pipe->logical_pipe_num, pipe->usb_pipe_handle,
			pipe->ep_address, nbytes));

		usb_hif_enqueue_pending_transfer(pipe, urb_context);
		usb_status = usb_submit_urb(urb, GFP_ATOMIC);
		if (usb_status) {
			if (pSendContext->bNewAlloc)
				adf_os_mem_free(pSendContext);
			else
				adf_nbuf_pull_head(buf, head_data_len);
			urb_context->buf = NULL;
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
						"athusb : usb bulk transmit failed %d\n",
						usb_status));
			usb_hif_remove_pending_transfer(urb_context);
			usb_hif_cleanup_transmit_urb(urb_context);
			status = A_ECOMM;
			break;
		}

	} while (FALSE);

exit:
	if (A_FAILED(status) && (status != A_NO_RESOURCE)) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
				("athusb send failed %d\n", status));
	}

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_OUT,
			("-%s pipe : %d\n", __func__, PipeID));

	return status;
}

/* Send the entire buffer */
A_STATUS HIFSend(HIF_DEVICE *hif_device, a_uint8_t pipe, adf_nbuf_t hdr_buf,
		 adf_nbuf_t netbuf)
{
	return HIFSend_head(hif_device, pipe, 0, adf_nbuf_len(netbuf), netbuf);
}

A_STATUS
HIFSend_head(HIF_DEVICE *hif_device,
	     a_uint8_t pipe, unsigned int transfer_id, unsigned int nbytes,
	     adf_nbuf_t nbuf)
{
	A_STATUS status = EOK;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	status = HIFSend_internal(hif_device, pipe, NULL, nbuf, nbytes);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));

	return status;
}

a_uint16_t HIFGetFreeQueueNumber(HIF_DEVICE *hifDevice, a_uint8_t PipeID)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;
	return device->pipes[PipeID].urb_cnt;
}

a_uint16_t HIFGetMaxQueueNumber(HIF_DEVICE *hifDevice, a_uint8_t PipeID)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;
	return device->pipes[PipeID].urb_alloc;
}

void HIFPostInit(HIF_DEVICE *hifDevice, void *unused,
		 MSG_BASED_HIF_CALLBACKS *callbacks)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;

	A_MEMCPY(&device->htcCallbacks.Context, callbacks,
		 sizeof(MSG_BASED_HIF_CALLBACKS));
}

void HIFDetachHTC(HIF_DEVICE *hifDevice)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;

	usb_hif_flush_all(device);

	A_MEMZERO(&device->htcCallbacks, sizeof(MSG_BASED_HIF_CALLBACKS));
}

static void usb_hif_destroy(HIF_DEVICE_USB *device)
{
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	usb_hif_flush_all(device);

	usb_hif_cleanup_pipe_resources(device);

	usb_set_intfdata(device->interface, NULL);

	if (device->diag_cmd_buffer != NULL)
		adf_os_mem_free(device->diag_cmd_buffer);

	if (device->diag_resp_buffer != NULL)
		adf_os_mem_free(device->diag_resp_buffer);

	adf_os_mem_free(device);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

static HIF_DEVICE_USB *usb_hif_create(struct usb_interface *interface)
{
	HIF_DEVICE_USB *device = NULL;
	struct usb_device *dev = interface_to_usbdev(interface);
	A_STATUS status = A_OK;
	int i;
	HIF_USB_PIPE *pipe;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	do {

		device = adf_os_mem_alloc(NULL, sizeof(*device));
		if (NULL == device) {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("device is NULL\n"));
			break;
		}

		adf_os_mem_zero(device, sizeof(*device));
		usb_set_intfdata(interface, device);
		adf_os_spinlock_init(&(device->cs_lock));
		adf_os_spinlock_init(&(device->rx_lock));
		adf_os_spinlock_init(&(device->tx_lock));
		adf_os_spinlock_init(&(device->rx_prestart_lock));
		device->udev = dev;
		device->interface = interface;

		for (i = 0; i < HIF_USB_PIPE_MAX; i++) {
			pipe = &device->pipes[i];
#ifdef HIF_USB_TASKLET
			tasklet_init(&pipe->io_complete_tasklet, usb_hif_io_comp_tasklet,
						(long unsigned int)pipe);
#else
			INIT_WORK(&pipe->io_complete_work,
				  usb_hif_io_comp_work);
#endif
			skb_queue_head_init(&pipe->io_comp_queue);
		}

		device->diag_cmd_buffer = adf_os_mem_alloc(NULL,
						USB_CTRL_MAX_DIAG_CMD_SIZE);
		if (NULL == device->diag_cmd_buffer) {
			status = A_NO_MEMORY;
			break;
		}
		device->diag_resp_buffer = adf_os_mem_alloc(NULL,
						USB_CTRL_MAX_DIAG_RESP_SIZE);
		if (NULL == device->diag_resp_buffer) {
			status = A_NO_MEMORY;
			break;
		}

		status = usb_hif_setup_pipe_resources(device);

	} while (FALSE);

	if (A_FAILED(status)) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("abnormal condition\n"));
		usb_hif_destroy(device);
		device = NULL;
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	return device;
}

A_STATUS HIFStart(HIF_DEVICE *hifDevice)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;
	int i;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	usb_hif_prestart_recv_pipes(device);

	/* set the TX resource avail threshold for each TX pipe */
	for (i = HIF_TX_CTRL_PIPE; i <= HIF_TX_DATA_HP_PIPE; i++) {
		device->pipes[i].urb_cnt_thresh =
		    device->pipes[i].urb_alloc / 2;
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	return A_OK;
}

void HIFStop(HIF_DEVICE *hifDevice)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hifDevice;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	usb_hif_flush_all(device);

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void HIFGetDefaultPipe(HIF_DEVICE *hifDevice, a_uint8_t *ULPipe,
		       a_uint8_t *DLPipe)
{
	*ULPipe = HIF_TX_CTRL_PIPE;
	*DLPipe = HIF_RX_CTRL_PIPE;
}

#if defined(USB_MULTI_IN_TEST) || defined(USB_ISOC_TEST)
int
HIFMapServiceToPipe(HIF_DEVICE *hif_device, a_uint16_t ServiceId,
		    a_uint8_t *ULPipe, a_uint8_t *DLPipe, int *ul_is_polled,
		    int *dl_is_polled)
{
	A_STATUS status = A_OK;

	switch (ServiceId) {
	case HTC_CTRL_RSVD_SVC:
	case WMI_CONTROL_SVC:
	case HTC_RAW_STREAMS_SVC:
		*ULPipe = HIF_TX_CTRL_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	case WMI_DATA_BE_SVC:
		*ULPipe = HIF_TX_DATA_LP_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	case WMI_DATA_BK_SVC:
		*ULPipe = HIF_TX_DATA_MP_PIPE;
		*DLPipe = HIF_RX_DATA2_PIPE;
		break;
	case WMI_DATA_VI_SVC:
		*ULPipe = HIF_TX_DATA_HP_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	case WMI_DATA_VO_SVC:
		*ULPipe = HIF_TX_DATA_LP_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	default:
		status = A_ENOTSUP;
		break;
	}

	return status;
}
#else
int
HIFMapServiceToPipe(HIF_DEVICE *hif_device, a_uint16_t ServiceId,
		    a_uint8_t *ULPipe, a_uint8_t *DLPipe, int *ul_is_polled,
		    int *dl_is_polled)
{
	A_STATUS status = A_OK;

	switch (ServiceId) {
	case HTC_CTRL_RSVD_SVC:
	case WMI_CONTROL_SVC:
		*ULPipe = HIF_TX_CTRL_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	case WMI_DATA_BE_SVC:
	case WMI_DATA_BK_SVC:
		*ULPipe = HIF_TX_DATA_LP_PIPE;
		if (hif_usb_disable_rxdata2)
			*DLPipe = HIF_RX_DATA_PIPE;
		else
			*DLPipe = HIF_RX_DATA2_PIPE;
		break;
	case WMI_DATA_VI_SVC:
#ifdef USB_HIF_USE_SINGLE_PIPE_FOR_DATA
		*ULPipe = HIF_TX_DATA_LP_PIPE;
#else
		*ULPipe = HIF_TX_DATA_MP_PIPE;
#endif
		if (hif_usb_disable_rxdata2)
			*DLPipe = HIF_RX_DATA_PIPE;
		else
			*DLPipe = HIF_RX_DATA2_PIPE;
		break;
	case WMI_DATA_VO_SVC:
#ifdef USB_HIF_USE_SINGLE_PIPE_FOR_DATA
		*ULPipe = HIF_TX_DATA_LP_PIPE;
#else
		*ULPipe = HIF_TX_DATA_HP_PIPE;
#endif
		if (hif_usb_disable_rxdata2)
			*DLPipe = HIF_RX_DATA_PIPE;
		else
			*DLPipe = HIF_RX_DATA2_PIPE;
#ifdef USB_HIF_TEST_INTERRUPT_IN
		*DLPipe = HIF_RX_INT_PIPE;
#endif
		break;
	case HTC_RAW_STREAMS_SVC:
		*ULPipe = HIF_TX_CTRL_PIPE;
		*DLPipe = HIF_RX_DATA_PIPE;
		break;
	case HTT_DATA_MSG_SVC:
		*ULPipe = HIF_TX_DATA_LP_PIPE;
		if (hif_usb_disable_rxdata2)
			*DLPipe = HIF_RX_DATA_PIPE;
		else
			*DLPipe = HIF_RX_DATA2_PIPE;
		break;
#ifdef QCA_TX_HTT2_SUPPORT
	case HTT_DATA2_MSG_SVC:
		*ULPipe = HIF_TX_DATA_HP_PIPE;
		if (hif_usb_disable_rxdata2)
			*DLPipe = HIF_RX_DATA_PIPE;
		else
			*DLPipe = HIF_RX_DATA2_PIPE;
		break;
#endif /* QCA_TX_HTT2_SUPPORT */
	default:
		status = A_ENOTSUP;
		break;
	}

	return status;
}
#endif

static A_STATUS HIFCtrlMsgExchange(HIF_DEVICE_USB *macp,
				   A_UINT8 SendReqVal,
				   A_UINT8 *pSendMessage,
				   A_UINT32 Length,
				   A_UINT8 ResponseReqVal,
				   A_UINT8 *pResponseMessage,
				   A_UINT32 *pResponseLength)
{
	A_STATUS status;

	do {

		/* send command */
		status = usb_hif_submit_ctrl_out(macp, SendReqVal, 0, 0,
						 pSendMessage, Length);

		if (A_FAILED(status))
			break;

		if (NULL == pResponseMessage) {
			/* no expected response */
			break;
		}

		/* get response */
		status = usb_hif_submit_ctrl_in(macp, ResponseReqVal, 0, 0,
						pResponseMessage,
						*pResponseLength);

		if (A_FAILED(status))
			break;

	} while (FALSE);

	return status;
}

A_STATUS HIFExchangeBMIMsg(HIF_DEVICE *device,
			   A_UINT8 *pSendMessage,
			   A_UINT32 Length,
			   A_UINT8 *pResponseMessage,
			   A_UINT32 *pResponseLength, A_UINT32 TimeoutMS)
{
	HIF_DEVICE_USB *macp = (HIF_DEVICE_USB *) device;

	return HIFCtrlMsgExchange(macp,
				  USB_CONTROL_REQ_SEND_BMI_CMD,
				  pSendMessage,
				  Length,
				  USB_CONTROL_REQ_RECV_BMI_RESP,
				  pResponseMessage, pResponseLength);
}

A_STATUS HIFConfigureDevice(HIF_DEVICE *hif, HIF_DEVICE_CONFIG_OPCODE opcode,
			    void *config, A_UINT32 configLen)
{
	A_STATUS status = A_OK;
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif;

	switch (opcode) {

	case HIF_DEVICE_GET_OS_DEVICE:
		{
			HIF_DEVICE_OS_DEVICE_INFO *info = config;
			info->pOSDevice = &device->udev->dev;
		}
		break;
	case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
		/* provide fake block sizes for mailboxes to satisfy upper layer
		 * software
		 */
		((A_UINT32 *) config)[0] = 16;
		((A_UINT32 *) config)[1] = 16;
		((A_UINT32 *) config)[2] = 16;
		((A_UINT32 *) config)[3] = 16;
		break;
	default:
		status = A_ENOTSUP;
		break;

	}

	return status;
}

A_STATUS hifWaitForPendingRecv(HIF_DEVICE *device)
{
	return A_OK;
}

A_STATUS HIFDiagReadAccess(HIF_DEVICE *hifDevice, A_UINT32 address,
			   A_UINT32 *data)
{
	HIF_DEVICE_USB *macp = (HIF_DEVICE_USB *) hifDevice;
	A_STATUS status;
	USB_CTRL_DIAG_CMD_READ *cmd;
	A_UINT32 respLength;

	cmd = (USB_CTRL_DIAG_CMD_READ *) macp->diag_cmd_buffer;

	A_MEMZERO(cmd, sizeof(*cmd));
	cmd->Cmd = USB_CTRL_DIAG_CC_READ;
	cmd->Address = address;
	respLength = sizeof(USB_CTRL_DIAG_RESP_READ);

	status = HIFCtrlMsgExchange(macp,
				    USB_CONTROL_REQ_DIAG_CMD,
				    (A_UINT8 *) cmd,
				    sizeof(*cmd),
				    USB_CONTROL_REQ_DIAG_RESP,
				    macp->diag_resp_buffer, &respLength);

	if (A_SUCCESS(status)) {
		USB_CTRL_DIAG_RESP_READ *pResp =
		    (USB_CTRL_DIAG_RESP_READ *) macp->diag_resp_buffer;
		*data = pResp->ReadValue;
	}

	return status;
}

A_STATUS HIFDiagWriteAccess(HIF_DEVICE *hifDevice, A_UINT32 address,
			    A_UINT32 data)
{
	HIF_DEVICE_USB *macp = (HIF_DEVICE_USB *) hifDevice;
	USB_CTRL_DIAG_CMD_WRITE *cmd;

	cmd = (USB_CTRL_DIAG_CMD_WRITE *) macp->diag_cmd_buffer;

	A_MEMZERO(cmd, sizeof(*cmd));
	cmd->Cmd = USB_CTRL_DIAG_CC_WRITE;
	cmd->Address = address;
	cmd->Value = data;

	return HIFCtrlMsgExchange(macp,
				  USB_CONTROL_REQ_DIAG_CMD,
				  (A_UINT8 *) cmd,
				  sizeof(*cmd), 0, NULL, 0);
}

void HIFShutDownDevice(HIF_DEVICE *hif)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	if (NULL == hif) {
		AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("full shutdown\n"));
		/* this is a full driver shutdown */
	} else {
		/* perform any actions to shutdown specific device */
		usb_hif_flush_all(device);
	}

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void HIFReleaseDevice(HIF_DEVICE *hif)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	device->claimed_context = NULL;
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void HIFClaimDevice(HIF_DEVICE *hif, void *claimedContext)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif;
	device->claimed_context = claimedContext;
}

A_STATUS HIFInit(OSDRV_CALLBACKS *callbacks)
{
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HIFInit\n"));

	A_MEMZERO(&osDrvcallback, sizeof(osDrvcallback));

	A_REGISTER_MODULE_DEBUG_INFO(hif);

	osDrvcallback.deviceInsertedHandler = callbacks->deviceInsertedHandler;
	osDrvcallback.deviceRemovedHandler = callbacks->deviceRemovedHandler;
	osDrvcallback.deviceSuspendHandler = callbacks->deviceSuspendHandler;
	osDrvcallback.deviceResumeHandler = callbacks->deviceResumeHandler;
	osDrvcallback.deviceWakeupHandler = callbacks->deviceWakeupHandler;
	osDrvcallback.context = callbacks->context;
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HIFInit\n"));
	return A_OK;
}

#ifdef ATH_BUS_PM
void HIFDeviceSuspend(HIF_DEVICE *dev)
{
	HIF_DEVICE_USB *macp = (HIF_DEVICE_USB *) dev;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	if (osDrvcallback.deviceSuspendHandler)
		osDrvcallback.deviceSuspendHandler(macp->claimed_context);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}

void HIFDeviceResume(HIF_DEVICE *dev)
{
	HIF_DEVICE_USB *macp = (HIF_DEVICE_USB *) dev;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	if (osDrvcallback.deviceResumeHandler)
		osDrvcallback.deviceResumeHandler(macp->claimed_context);
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
}
#endif

void HIFDumpInfo(HIF_DEVICE *hif)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif;
	HIF_USB_PIPE *pipe = NULL;
	struct usb_host_interface *iface_desc = NULL;
	struct usb_endpoint_descriptor *ep_desc;
	A_UINT8 i = 0;
	for (i = 0; i < HIF_USB_PIPE_MAX; i++) {
		pipe = &device->pipes[i];
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (
				 "PipeIndex : %d URB Cnt : %d PipeHandle : %x\n",
				 i, pipe->urb_cnt,
				 pipe->usb_pipe_handle));
		if (usb_pipeisoc(pipe->usb_pipe_handle))
			pr_info("Pipe Type ISOC\n");
		else if (usb_pipebulk(pipe->usb_pipe_handle))
			pr_info("Pipe Type BULK\n");
		else if (usb_pipeint(pipe->usb_pipe_handle))
			pr_info("Pipe Type INT\n");
		else if (usb_pipecontrol(pipe->usb_pipe_handle))
			pr_info("Pipe Type control\n");
	}

	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
		ep_desc = &iface_desc->endpoint[i].desc;
		if (ep_desc) {
			pr_info(
			     "ep_desc : %pK Index : %d: DescType : %d Addr : %d Maxp : %d Atrrib : %d\n",
			     ep_desc, i,
			     ep_desc->bDescriptorType,
			     ep_desc->bEndpointAddress, ep_desc->wMaxPacketSize,
			     ep_desc->bmAttributes);
			if ((ep_desc)
			    && (usb_endpoint_type(ep_desc) ==
				USB_ENDPOINT_XFER_ISOC)) {
				pr_info("ISOC EP Detected\n");
			}
		}
	}

}

void HIFDump(HIF_DEVICE *hif_device, u_int8_t cmd_id, bool start)
{
/* TO DO... */
}

void HIFFlushSurpriseRemove(HIF_DEVICE *hif_device)
{
/* TO DO... */
}

A_STATUS
HIFDiagReadMem(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT8 *data,
	       int nbytes)
{
	A_STATUS status = EOK;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	if ((address & 0x3) || ((uintptr_t)data & 0x3)) {
		return (-EIO);
	}

	while ((nbytes >= 4) &&
			(A_OK == (status = HIFDiagReadAccess(hif_device, address,
					   (A_UINT32*)data)))) {

		nbytes -= sizeof(A_UINT32);
		address += sizeof(A_UINT32);
		data   += sizeof(A_UINT32);

	}
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	return status;
}

A_STATUS
HIFDiagWriteMem(HIF_DEVICE *hif_device, A_UINT32 address, A_UINT8 *data, int nbytes)
{
	A_STATUS status = EOK;

	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));

	if ((address & 0x3) || ((uintptr_t)data & 0x3)) {
		return (-EIO);
	}

	while ((nbytes >= 4) &&
			(A_OK == (status = HIFDiagWriteAccess(hif_device, address,
					   *((A_UINT32*)data))))) {

		nbytes -= sizeof(A_UINT32);
		address += sizeof(A_UINT32);
		data   += sizeof(A_UINT32);

	}
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	return status;
}

void *hif_get_targetdef(HIF_DEVICE *hif_device)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif_device;
	struct hif_usb_softc *sc = device->sc;

	return sc->targetdef;
}

void HIFSendCompleteCheck(HIF_DEVICE *hif_device, a_uint8_t pipe, int force)
{
}

void HIFCancelDeferredTargetSleep(HIF_DEVICE *hif_device)
{
}

/* diagnostic command defnitions */
#define USB_CTRL_DIAG_CC_READ       0
#define USB_CTRL_DIAG_CC_WRITE      1
#define USB_CTRL_DIAG_CC_WARM_RESET 2
A_STATUS HIFDiagWriteWARMRESET(struct usb_interface *interface,
			       A_UINT32 address, A_UINT32 data)
{
	HIF_DEVICE_USB *hHIF_DEV;
	A_STATUS status = EOK;
	USB_CTRL_DIAG_CMD_WRITE *cmd;

	hHIF_DEV = (HIF_DEVICE_USB *) usb_get_intfdata(interface);
	cmd = (USB_CTRL_DIAG_CMD_WRITE *) hHIF_DEV->diag_cmd_buffer;
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+%s\n", __func__));
	A_MEMZERO(cmd, sizeof(USB_CTRL_DIAG_CMD_WRITE));
	cmd->Cmd = USB_CTRL_DIAG_CC_WARM_RESET;
	cmd->Address = address;
	cmd->Value = data;

	status = HIFCtrlMsgExchange(hHIF_DEV,
				    USB_CONTROL_REQ_DIAG_CMD,
				    (A_UINT8 *) cmd,
				    sizeof(USB_CTRL_DIAG_CMD_WRITE),
				    0, NULL, 0);
	if (A_FAILED(status)) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("-%s HIFCtrlMsgExchange fail\n",
						__func__));
	}
	AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-%s\n", __func__));
	return status;
}
void HIFsuspendwow(HIF_DEVICE *hif_device)
{
	printk(KERN_INFO "HIFsuspendwow TODO\n");
}

void HIFSetBundleMode(HIF_DEVICE *hif_device, bool enabled, int rx_bundle_cnt)
{
	HIF_DEVICE_USB *device = (HIF_DEVICE_USB *) hif_device;

	device->is_bundle_enabled = enabled;
	device->rx_bundle_cnt = rx_bundle_cnt;
	if (device->is_bundle_enabled && (device->rx_bundle_cnt == 0)) {
		device->rx_bundle_cnt = 1;
	}
	device->rx_bundle_buf_len = device->rx_bundle_cnt *
		                        HIF_USB_RX_BUNDLE_ONE_PKT_SIZE;

	AR_DEBUG_PRINTF(USB_HIF_DEBUG_BULK_IN,
			("athusb bundle %s cnt %d\n",
			 enabled ? "enabled" : "disabled",
			 rx_bundle_cnt));
}

/**
 * hif_is_80211_fw_wow_required() - API to check if target suspend is needed
 *
 * API determines if fw can be suspended and returns true/false to the caller.
 * Caller will call WMA WoW API's to suspend.
 * This API returns true only for SDIO bus types, for others it's a false.
 *
 * Return: bool
 */
bool hif_is_80211_fw_wow_required(void)
{
	return true;
}
