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

#include "targcfg.h"
#include "target_type.h"
#include "qdf_lock.h"
#include "qdf_status.h"
#include "qdf_status.h"
#include <qdf_atomic.h>         /* qdf_atomic_read */
#include <targaddrs.h>
#include "hif_io32.h"
#include <hif.h>
#include "regtable.h"
#include <a_debug.h>
#include "hif_main.h"
#include "ce_api.h"
#include "qdf_trace.h"
#include "hif_debug.h"
#include "qdf_module.h"

void
hif_ce_dump_target_memory(struct hif_softc *scn, void *ramdump_base,
						uint32_t address, uint32_t size)
{
	uint32_t loc = address;
	uint32_t val = 0;
	uint32_t j = 0;
	u8 *temp = ramdump_base;

	if (Q_TARGET_ACCESS_BEGIN(scn) < 0)
		return;

	while (j < size) {
		val = hif_read32_mb(scn->mem + loc + j);
		qdf_mem_copy(temp, &val, 4);
		j += 4;
		temp += 4;
	}

	Q_TARGET_ACCESS_END(scn);
}
/*
 * TBDXXX: Should be a function call specific to each Target-type.
 * This convoluted macro converts from Target CPU Virtual Address
 * Space to CE Address Space. As part of this process, we
 * conservatively fetch the current PCIE_BAR. MOST of the time,
 * this should match the upper bits of PCI space for this device;
 * but that's not guaranteed.
 */
#ifdef QCA_WIFI_3_0
#define TARG_CPU_SPACE_TO_CE_SPACE(pci_addr, addr) \
	(scn->mem_pa + addr)
#else
#define TARG_CPU_SPACE_TO_CE_SPACE(pci_addr, addr) \
	(((hif_read32_mb((pci_addr) + \
	(SOC_CORE_BASE_ADDRESS|CORE_CTRL_ADDRESS)) & 0x7ff) << 21) \
	 | 0x100000 | ((addr) & 0xfffff))
#endif

#define TARG_CPU_SPACE_TO_CE_SPACE_IPQ4019(pci_addr, addr) \
	(hif_read32_mb((pci_addr)+(WIFICMN_PCIE_BAR_REG_ADDRESS)) \
	| ((addr) & 0xfffff))

#define TARG_CPU_SPACE_TO_CE_SPACE_AR900B(pci_addr, addr) \
	(hif_read32_mb((pci_addr)+(WIFICMN_PCIE_BAR_REG_ADDRESS)) \
	| 0x100000 | ((addr) & 0xfffff))

#define SRAM_BASE_ADDRESS 0xc0000
#define SRAM_END_ADDRESS 0x100000
#define WIFI0_IPQ4019_BAR 0xa000000
#define WIFI1_IPQ4019_BAR 0xa800000

/* Wait up to this many Ms for a Diagnostic Access CE operation to complete */
#define DIAG_ACCESS_CE_TIMEOUT_MS 10

/**
 * get_ce_phy_addr() - get the physical address of an soc virtual address
 * @sc: hif context
 * @address: soc virtual address
 * @target_type: target type being used.
 *
 * Return: soc physical address
 */
static qdf_dma_addr_t get_ce_phy_addr(struct hif_softc *sc, uint32_t  address,
				      unsigned int target_type)
{
	qdf_dma_addr_t ce_phy_addr;
	struct hif_softc *scn = sc;
	unsigned int region = address & 0xfffff;
	unsigned int bar = address & 0xfff00000;
	unsigned int sramregion = 0;

	if ((target_type == TARGET_TYPE_IPQ4019) &&
		(region >= SRAM_BASE_ADDRESS && region <= SRAM_END_ADDRESS)
		&& (bar == WIFI0_IPQ4019_BAR ||
		    bar == WIFI1_IPQ4019_BAR || bar == 0)) {
		sramregion = 1;
	}

	if ((target_type == TARGET_TYPE_IPQ4019) && sramregion == 1) {
		ce_phy_addr =
			TARG_CPU_SPACE_TO_CE_SPACE_IPQ4019(sc->mem, address);
	} else if ((target_type == TARGET_TYPE_AR900B) ||
	    (target_type == TARGET_TYPE_QCA9984) ||
	    (target_type == TARGET_TYPE_IPQ4019) ||
	    (target_type == TARGET_TYPE_QCA9888)) {
		ce_phy_addr =
		    TARG_CPU_SPACE_TO_CE_SPACE_AR900B(sc->mem, address);
	} else {
		ce_phy_addr =
		    TARG_CPU_SPACE_TO_CE_SPACE(sc->mem, address);
	}

	return ce_phy_addr;
}

/*
 * Diagnostic read/write access is provided for startup/config/debug usage.
 * Caller must guarantee proper alignment, when applicable, and single user
 * at any moment.
 */

#define FW_SRAM_ADDRESS     0x000C0000

QDF_STATUS hif_diag_read_mem(struct hif_opaque_softc *hif_ctx,
			     uint32_t address, uint8_t *data, int nbytes)
{
	struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);

	struct HIF_CE_state *hif_state = HIF_GET_CE_STATE(scn);
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	qdf_dma_addr_t buf;
	unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
	unsigned int id;
	unsigned int flags;
	struct CE_handle *ce_diag;
	qdf_dma_addr_t CE_data;      /* Host buffer address in CE space */
	qdf_dma_addr_t CE_data_base = 0;
	void *data_buf = NULL;
	int i;
	unsigned int mux_id = 0;
	unsigned int transaction_id = 0xffff;
	qdf_dma_addr_t ce_phy_addr = address;
	unsigned int toeplitz_hash_result;
	unsigned int user_flags = 0;
	unsigned int target_type = 0;
	unsigned int boundary_addr = 0;

	ce_diag = hif_state->ce_diag;
	if (ce_diag == NULL) {
		HIF_ERROR("%s: DIAG CE not present", __func__);
		return QDF_STATUS_E_INVAL;
	}
	/* not supporting diag ce on srng based systems, therefore we know this
	 * isn't an srng based system */

	transaction_id = (mux_id & MUX_ID_MASK) |
		 (transaction_id & TRANSACTION_ID_MASK);
#ifdef QCA_WIFI_3_0
	user_flags &= DESC_DATA_FLAG_MASK;
#endif
	target_type = (hif_get_target_info_handle(hif_ctx))->target_type;

	/* This code cannot handle reads to non-memory space. Redirect to the
	 * register read fn but preserve the multi word read capability of
	 * this fn
	 */
	if ((target_type == TARGET_TYPE_IPQ4019) ||
	    (target_type == TARGET_TYPE_AR900B)  ||
	    (target_type == TARGET_TYPE_QCA9984) ||
	    (target_type == TARGET_TYPE_AR9888) ||
	    (target_type == TARGET_TYPE_QCA9888))
		boundary_addr = FW_SRAM_ADDRESS;
	else
		boundary_addr = DRAM_BASE_ADDRESS;

	if (address < boundary_addr) {

		if ((address & 0x3) || ((uintptr_t) data & 0x3))
			return QDF_STATUS_E_INVAL;

		while ((nbytes >= 4) &&
		       (QDF_STATUS_SUCCESS == (status =
				 hif_diag_read_access(hif_ctx, address,
				       (uint32_t *)data)))) {

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

		}

		return status;
	}

	A_TARGET_ACCESS_LIKELY(scn);

	/*
	 * Allocate a temporary bounce buffer to hold caller's data
	 * to be DMA'ed from Target. This guarantees
	 *   1) 4-byte alignment
	 *   2) Buffer in DMA-able space
	 */
	orig_nbytes = nbytes;
	data_buf = qdf_mem_alloc_consistent(scn->qdf_dev, scn->qdf_dev->dev,
				    orig_nbytes, &CE_data_base);
	if (!data_buf) {
		status = QDF_STATUS_E_NOMEM;
		goto done;
	}
	qdf_mem_set(data_buf, orig_nbytes, 0);
	qdf_mem_dma_sync_single_for_device(scn->qdf_dev, CE_data_base,
				       orig_nbytes, DMA_FROM_DEVICE);

	remaining_bytes = orig_nbytes;
	CE_data = CE_data_base;
	while (remaining_bytes) {
		nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT);
		{
			status = ce_recv_buf_enqueue(ce_diag, NULL, CE_data);
			if (status != QDF_STATUS_SUCCESS)
				goto done;
		}

		if (Q_TARGET_ACCESS_BEGIN(scn) < 0) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		/* convert soc virtual address to physical address */
		ce_phy_addr = get_ce_phy_addr(scn, address, target_type);

		if (Q_TARGET_ACCESS_END(scn) < 0) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		/* Request CE to send from Target(!)
		 * address to Host buffer
		 */
		status = ce_send(ce_diag, NULL, ce_phy_addr, nbytes,
				transaction_id, 0, user_flags);
		if (status != QDF_STATUS_SUCCESS)
			goto done;

		i = 0;
		while (ce_completed_send_next(ce_diag, NULL, NULL, &buf,
				&completed_nbytes, &id, NULL, NULL,
				&toeplitz_hash_result) != QDF_STATUS_SUCCESS) {
			qdf_mdelay(1);
			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
				status = QDF_STATUS_E_BUSY;
				goto done;
			}
		}
		if (nbytes != completed_nbytes) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}
		if (buf != ce_phy_addr) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		i = 0;
		while (ce_completed_recv_next
				(ce_diag, NULL, NULL, &buf,
				&completed_nbytes, &id,
				 &flags) != QDF_STATUS_SUCCESS) {
			qdf_mdelay(1);
			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
				status = QDF_STATUS_E_BUSY;
				goto done;
			}
		}
		if (nbytes != completed_nbytes) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}
		if (buf != CE_data) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		remaining_bytes -= nbytes;
		address += nbytes;
		CE_data += nbytes;
	}

done:
	A_TARGET_ACCESS_UNLIKELY(scn);

	if (status == QDF_STATUS_SUCCESS)
		qdf_mem_copy(data, data_buf, orig_nbytes);
	else
		HIF_ERROR("%s failure (0x%x)", __func__, address);

	if (data_buf)
		qdf_mem_free_consistent(scn->qdf_dev, scn->qdf_dev->dev,
				orig_nbytes, data_buf, CE_data_base, 0);

	return status;
}
qdf_export_symbol(hif_diag_read_mem);

/* Read 4-byte aligned data from Target memory or register */
QDF_STATUS hif_diag_read_access(struct hif_opaque_softc *hif_ctx,
				uint32_t address, uint32_t *data)
{
	struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);

	if (address >= DRAM_BASE_ADDRESS) {
		/* Assume range doesn't cross this boundary */
		return hif_diag_read_mem(hif_ctx, address, (uint8_t *) data,
					 sizeof(uint32_t));
	} else {
		if (Q_TARGET_ACCESS_BEGIN(scn) < 0)
			return QDF_STATUS_E_FAILURE;
		*data = A_TARGET_READ(scn, address);
		if (Q_TARGET_ACCESS_END(scn) < 0)
			return QDF_STATUS_E_FAILURE;

		return QDF_STATUS_SUCCESS;
	}
}

/**
 * hif_diag_write_mem() - write data into the soc memory
 * @hif_ctx: hif context
 * @address: soc virtual address
 * @data: data to copy into the soc address
 * @nbytes: number of bytes to coppy
 */
QDF_STATUS hif_diag_write_mem(struct hif_opaque_softc *hif_ctx,
			      uint32_t address, uint8_t *data, int nbytes)
{
	struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);
	struct HIF_CE_state *hif_state = HIF_GET_CE_STATE(hif_ctx);
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	qdf_dma_addr_t buf;
	unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
	unsigned int id;
	unsigned int flags;
	struct CE_handle *ce_diag;
	void *data_buf = NULL;
	qdf_dma_addr_t CE_data;      /* Host buffer address in CE space */
	qdf_dma_addr_t CE_data_base = 0;
	int i;
	unsigned int mux_id = 0;
	unsigned int transaction_id = 0xffff;
	qdf_dma_addr_t ce_phy_addr = address;
	unsigned int toeplitz_hash_result;
	unsigned int user_flags = 0;
	unsigned int target_type = 0;

	ce_diag = hif_state->ce_diag;
	if (ce_diag == NULL) {
		HIF_ERROR("%s: DIAG CE not present", __func__);
		return QDF_STATUS_E_INVAL;
	}
	/* not supporting diag ce on srng based systems, therefore we know this
	 * isn't an srng based system */

	transaction_id = (mux_id & MUX_ID_MASK) |
		(transaction_id & TRANSACTION_ID_MASK);
#ifdef QCA_WIFI_3_0
	user_flags &= DESC_DATA_FLAG_MASK;
#endif

	A_TARGET_ACCESS_LIKELY(scn);

	/*
	 * Allocate a temporary bounce buffer to hold caller's data
	 * to be DMA'ed to Target. This guarantees
	 *   1) 4-byte alignment
	 *   2) Buffer in DMA-able space
	 */
	orig_nbytes = nbytes;
	data_buf = qdf_mem_alloc_consistent(scn->qdf_dev, scn->qdf_dev->dev,
				    orig_nbytes, &CE_data_base);
	if (!data_buf) {
		status = QDF_STATUS_E_NOMEM;
		goto done;
	}

	/* Copy caller's data to allocated DMA buf */
	qdf_mem_copy(data_buf, data, orig_nbytes);
	qdf_mem_dma_sync_single_for_device(scn->qdf_dev, CE_data_base,
				       orig_nbytes, DMA_TO_DEVICE);

	target_type = (hif_get_target_info_handle(hif_ctx))->target_type;

	if (Q_TARGET_ACCESS_BEGIN(scn) < 0) {
		status = QDF_STATUS_E_FAILURE;
		goto done;
	}

	/* convert soc virtual address to physical address */
	ce_phy_addr = get_ce_phy_addr(scn, address, target_type);

	if (Q_TARGET_ACCESS_END(scn) < 0) {
		status = QDF_STATUS_E_FAILURE;
		goto done;
	}

	remaining_bytes = orig_nbytes;
	CE_data = CE_data_base;
	while (remaining_bytes) {
		nbytes = min(remaining_bytes, DIAG_TRANSFER_LIMIT);

		/* Set up to receive directly into Target(!) address */
		status = ce_recv_buf_enqueue(ce_diag, NULL, ce_phy_addr);
		if (status != QDF_STATUS_SUCCESS)
			goto done;

		/*
		 * Request CE to send caller-supplied data that
		 * was copied to bounce buffer to Target(!) address.
		 */
		status = ce_send(ce_diag, NULL, (qdf_dma_addr_t) CE_data,
				 nbytes, transaction_id, 0, user_flags);

		if (status != QDF_STATUS_SUCCESS)
			goto done;

		/* poll for transfer complete */
		i = 0;
		while (ce_completed_send_next(ce_diag, NULL, NULL, &buf,
			    &completed_nbytes, &id,
			    NULL, NULL, &toeplitz_hash_result) !=
			    QDF_STATUS_SUCCESS) {
			qdf_mdelay(1);
			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
				status = QDF_STATUS_E_BUSY;
				goto done;
			}
		}

		if (nbytes != completed_nbytes) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		if (buf != CE_data) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		i = 0;
		while (ce_completed_recv_next
			(ce_diag, NULL, NULL, &buf,
			&completed_nbytes, &id,
			&flags) != QDF_STATUS_SUCCESS) {
			qdf_mdelay(1);
			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
				status = QDF_STATUS_E_BUSY;
				goto done;
			}
		}

		if (nbytes != completed_nbytes) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		if (buf != ce_phy_addr) {
			status = QDF_STATUS_E_FAILURE;
			goto done;
		}

		remaining_bytes -= nbytes;
		address += nbytes;
		CE_data += nbytes;
	}

done:
	A_TARGET_ACCESS_UNLIKELY(scn);

	if (data_buf) {
		qdf_mem_free_consistent(scn->qdf_dev, scn->qdf_dev->dev,
				orig_nbytes, data_buf, CE_data_base, 0);
	}

	if (status != QDF_STATUS_SUCCESS) {
		HIF_ERROR("%s failure (0x%llx)", __func__,
			(uint64_t)ce_phy_addr);
	}

	return status;
}

/* Write 4B data to Target memory or register */
QDF_STATUS hif_diag_write_access(struct hif_opaque_softc *hif_ctx,
				 uint32_t address, uint32_t data)
{
	struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx);

	if (address >= DRAM_BASE_ADDRESS) {
		/* Assume range doesn't cross this boundary */
		uint32_t data_buf = data;

		return hif_diag_write_mem(hif_ctx, address,
					  (uint8_t *) &data_buf,
					  sizeof(uint32_t));
	} else {
		if (Q_TARGET_ACCESS_BEGIN(scn) < 0)
			return QDF_STATUS_E_FAILURE;
		A_TARGET_WRITE(scn, address, data);
		if (Q_TARGET_ACCESS_END(scn) < 0)
			return QDF_STATUS_E_FAILURE;

		return QDF_STATUS_SUCCESS;
	}
}
