/*
 * Copyright (c) 2013-2018 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.
 */

#define ATH_MODULE_NAME hif
#include <qdf_types.h>
#include <qdf_status.h>
#include <qdf_timer.h>
#include <qdf_time.h>
#include <qdf_lock.h>
#include <qdf_mem.h>
#include <qdf_util.h>
#include <qdf_defer.h>
#include <qdf_atomic.h>
#include <qdf_nbuf.h>
#include <athdefs.h>
#include <qdf_net_types.h>
#include <a_types.h>
#include <athdefs.h>
#include <a_osapi.h>
#include <hif.h>
#include <htc_services.h>
#include <a_debug.h>
#include "hif_sdio_internal.h"

/*
 * Data structure to record required sending context data
 */
struct hif_sendContext {
	bool bNewAlloc;
	struct hif_sdio_device *pDev;
	qdf_nbuf_t netbuf;
	unsigned int transferID;
	unsigned int head_data_len;
};

/**
 * hif_dev_rw_completion_handler() - Completion routine
 * for ALL HIF layer async I/O
 * @context: hif send context
 * @status: completion routine sync/async context
 *
 * Return: 0 for success and non-zero for failure
 */
QDF_STATUS hif_dev_rw_completion_handler(void *context, QDF_STATUS status)
{
	struct hif_sendContext *send_context =
				(struct hif_sendContext *)context;
	unsigned int transfer_id = send_context->transferID;
	struct hif_sdio_device *pdev = send_context->pDev;
	qdf_nbuf_t buf = send_context->netbuf;
	/* Fix Me: Do we need toeplitz_hash_result for SDIO */
	uint32_t toeplitz_hash_result = 0;

	if (send_context->bNewAlloc)
		qdf_mem_free((void *)send_context);
	else
		qdf_nbuf_pull_head(buf, send_context->head_data_len);
	if (pdev->hif_callbacks.txCompletionHandler)
		pdev->hif_callbacks.txCompletionHandler(pdev->hif_callbacks.
					Context, buf,
					transfer_id, toeplitz_hash_result);

	return QDF_STATUS_SUCCESS;
}

/**
 * hif_dev_send_buffer() - send buffer to sdio device
 * @pDev: sdio function
 * @transferID: transfer id
 * @pipe: ul/dl pipe
 * @nbytes: no of bytes to transfer
 * @buf: pointer to buffer
 *
 * Return: 0 for success and non-zero for failure
 */
QDF_STATUS hif_dev_send_buffer(struct hif_sdio_device *pdev,
			       unsigned int transfer_id,
			       uint8_t pipe, unsigned int nbytes,
			       qdf_nbuf_t buf)
{
	QDF_STATUS status;
	uint32_t padded_length;
	int frag_count = 0, i, head_data_len;
	struct hif_sendContext *send_context;
	unsigned char *pData;
	uint32_t request = HIF_WR_ASYNC_BLOCK_INC;
	uint8_t mbox_index = hif_dev_map_pipe_to_mail_box(pdev, pipe);

	if (mbox_index == INVALID_MAILBOX_NUMBER) {
		AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("pipe id(%d) invalid\n", pipe));
		return QDF_STATUS_E_FAILURE;
	}

	padded_length = DEV_CALC_SEND_PADDED_LEN(pdev, nbytes);
	A_ASSERT(padded_length - nbytes < HIF_DUMMY_SPACE_MASK + 1);
	/*
	 * two most significant bytes to save dummy data count
	 * data written into the dummy space will not put into
	 * the final mbox FIFO.
	 */
	request |= ((padded_length - nbytes) << 16);

	frag_count = qdf_nbuf_get_num_frags(buf);

	if (frag_count > 1) {
		/* header data length should be total sending length substract
		 * internal data length of netbuf
		 */
		head_data_len = sizeof(struct hif_sendContext) +
			(nbytes - qdf_nbuf_get_frag_len(buf, frag_count - 1));
	} else {
		/*
		 * | hif_sendContext | netbuf->data
		 */
		head_data_len = sizeof(struct hif_sendContext);
	}

	/* Check whether head room is enough to save extra head data */
	if ((head_data_len <= qdf_nbuf_headroom(buf)) &&
	    (qdf_nbuf_tailroom(buf) >= (padded_length - nbytes))) {
		send_context =
			(struct hif_sendContext *)qdf_nbuf_push_head(buf,
						     head_data_len);
		send_context->bNewAlloc = false;
	} else {
		send_context =
			(struct hif_sendContext *)
			qdf_mem_malloc(sizeof(struct hif_sendContext) +
				       padded_length);
		if (send_context) {
			send_context->bNewAlloc = true;
		} else {
			AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
				("Allocate send context fail %d\n",
				(int) sizeof(struct hif_sendContext) +
				padded_length));
			return QDF_STATUS_E_NOMEM;
		}
	}

	send_context->netbuf = buf;
	send_context->pDev = pdev;
	send_context->transferID = transfer_id;
	send_context->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 *)send_context + sizeof(struct hif_sendContext);
	for (i = 0; i < (send_context->bNewAlloc ? frag_count : frag_count - 1);
	     i++) {
		int frag_len = qdf_nbuf_get_frag_len(buf, i);
		unsigned char *frag_addr = qdf_nbuf_get_frag_vaddr(buf, i);

		if (frag_len > nbytes)
			frag_len = nbytes;
		memcpy(pData, frag_addr, frag_len);
		pData += frag_len;
		nbytes -= frag_len;
		if (nbytes <= 0)
			break;
	}

	/* Reset pData pointer and send_context out */
	pData = (unsigned char *)send_context + sizeof(struct hif_sendContext);
	status = hif_read_write(pdev->HIFDevice,
				pdev->MailBoxInfo.mbox_prop[mbox_index].
				extended_address, (char *)pData, padded_length,
				request, (void *)send_context);

	if (status == QDF_STATUS_E_PENDING)
		/*
		 * it will return QDF_STATUS_E_PENDING in native HIF
		 * implementation, which should be treated as successful
		 * result here.
		 */
		status = QDF_STATUS_SUCCESS;
	/* release buffer or move back data pointer when failed */
	if (status != QDF_STATUS_SUCCESS) {
		if (send_context->bNewAlloc)
			qdf_mem_free(send_context);
		else
			qdf_nbuf_pull_head(buf, head_data_len);
	}

	return status;
}
