/*
 * 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.
 */

#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif

#include <osdep.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/if_arp.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sd.h>
#include <linux/wait.h>
#include <qdf_mem.h>
#include "bmi_msg.h"            /* TARGET_TYPE_ */
#include "if_sdio.h"
#include <qdf_trace.h>
#include <cds_api.h>
#include "regtable_sdio.h"
#include <hif_debug.h>
#ifndef REMOVE_PKT_LOG
#include "ol_txrx_types.h"
#include "pktlog_ac_api.h"
#include "pktlog_ac.h"
#endif
#include "epping_main.h"

#ifndef ATH_BUS_PM
#ifdef CONFIG_PM
#define ATH_BUS_PM
#endif /* CONFIG_PM */
#endif /* ATH_BUS_PM */

#ifndef REMOVE_PKT_LOG
struct ol_pl_os_dep_funcs *g_ol_pl_os_dep_funcs = NULL;
#endif
#define HIF_SDIO_LOAD_TIMEOUT 1000

struct hif_sdio_softc *scn = NULL;
struct hif_softc *ol_sc;
static atomic_t hif_sdio_load_state;
/* Wait queue for MC thread */
wait_queue_head_t sync_wait_queue;

/**
 * hif_sdio_probe() - configure sdio device
 * @context: sdio device context
 * @hif_handle: pointer to hif handle
 *
 * Return: 0 for success and non-zero for failure
 */
static A_STATUS hif_sdio_probe(void *context, void *hif_handle)
{
	int ret = 0;
	struct HIF_DEVICE_OS_DEVICE_INFO os_dev_info;
	struct sdio_func *func = NULL;
	const struct sdio_device_id *id;
	uint32_t target_type;
	HIF_ENTER();

	scn = (struct hif_sdio_softc *)qdf_mem_malloc(sizeof(*scn));
	if (!scn) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	scn->hif_handle = hif_handle;
	hif_configure_device(hif_handle, HIF_DEVICE_GET_OS_DEVICE,
			     &os_dev_info,
			     sizeof(os_dev_info));

	scn->aps_osdev.device = os_dev_info.os_dev;
	scn->aps_osdev.bc.bc_bustype = QDF_BUS_TYPE_SDIO;
	spin_lock_init(&scn->target_lock);
	ol_sc = qdf_mem_malloc(sizeof(*ol_sc));
	if (!ol_sc) {
		ret = -ENOMEM;
		goto err_attach;
	}
	OS_MEMZERO(ol_sc, sizeof(*ol_sc));

	{
		/*
		 * Attach Target register table. This is needed early on
		 * even before BMI since PCI and HIF initialization
		 * directly access Target registers.
		 *
		 * TBDXXX: targetdef should not be global -- should be stored
		 * in per-device struct so that we can support multiple
		 * different Target types with a single Host driver.
		 * The whole notion of an "hif type" -- (not as in the hif
		 * module, but generic "Host Interface Type") is bizarre.
		 * At first, one one expect it to be things like SDIO, USB, PCI.
		 * But instead, it's an actual platform type. Inexplicably, the
		 * values used for HIF platform types are *different* from the
		 * values used for Target Types.
		 */

#if defined(CONFIG_AR9888_SUPPORT)
		hif_register_tbl_attach(ol_sc, HIF_TYPE_AR9888);
		target_register_tbl_attach(ol_sc, TARGET_TYPE_AR9888);
		target_type = TARGET_TYPE_AR9888;
#elif defined(CONFIG_AR6320_SUPPORT)
		id = ((struct hif_sdio_dev *) hif_handle)->id;
		if (((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) ==
				MANUFACTURER_ID_QCA9377_BASE) ||
			((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) ==
				MANUFACTURER_ID_QCA9379_BASE)) {
			hif_register_tbl_attach(ol_sc, HIF_TYPE_AR6320V2);
			target_register_tbl_attach(ol_sc, TARGET_TYPE_AR6320V2);
		} else if ((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) ==
				MANUFACTURER_ID_AR6320_BASE) {
			int ar6kid = id->device & MANUFACTURER_ID_AR6K_REV_MASK;
			if (ar6kid >= 1) {
				/* v2 or higher silicon */
				hif_register_tbl_attach(ol_sc,
					HIF_TYPE_AR6320V2);
				target_register_tbl_attach(ol_sc,
					  TARGET_TYPE_AR6320V2);
			} else {
				/* legacy v1 silicon */
				hif_register_tbl_attach(ol_sc,
					HIF_TYPE_AR6320);
				target_register_tbl_attach(ol_sc,
					  TARGET_TYPE_AR6320);
			}
		}
		target_type = TARGET_TYPE_AR6320;

#endif
	}
	func = ((struct hif_sdio_dev *) hif_handle)->func;
	scn->targetdef =  ol_sc->targetdef;
	scn->hostdef =  ol_sc->hostdef;
	scn->aps_osdev.bdev = func;
	ol_sc->bus_type = scn->aps_osdev.bc.bc_bustype;
	scn->ol_sc = *ol_sc;
	ol_sc->target_info.target_type = target_type;

#ifndef TARGET_DUMP_FOR_NON_QC_PLATFORM
	scn->ramdump_base = ioremap(RAMDUMP_ADDR, RAMDUMP_SIZE);
	scn->ramdump_size = RAMDUMP_SIZE;
	if (scn->ramdump_base == NULL) {
		scn->ramdump_base = 0;
		scn->ramdump_size = 0;
	}
#endif

	if (athdiag_procfs_init(scn) != 0) {
		QDF_TRACE(QDF_MODULE_ID_HIF, QDF_TRACE_LEVEL_ERROR,
			  "%s athdiag_procfs_init failed", __func__);
		ret = QDF_STATUS_E_FAILURE;
		goto err_attach1;
	}

	atomic_set(&hif_sdio_load_state, true);
	wake_up_interruptible(&sync_wait_queue);

	return 0;

err_attach1:
	qdf_mem_free(ol_sc);
err_attach:
	qdf_mem_free(scn);
	scn = NULL;
err_alloc:
	return ret;
}

/**
 * ol_ath_sdio_configure() - configure sdio device
 * @hif_sc: pointer to sdio softc structure
 * @dev: pointer to net device
 * @hif_handle: pointer to sdio function
 *
 * Return: 0 for success and non-zero for failure
 */
int
ol_ath_sdio_configure(void *hif_sc, struct net_device *dev,
		      hif_handle_t *hif_hdl)
{
	struct hif_sdio_softc *sc = (struct hif_sdio_softc *)hif_sc;
	int ret = 0;

	sc->aps_osdev.netdev = dev;
	*hif_hdl = sc->hif_handle;

	return ret;
}

/**
 * hif_sdio_remove() - remove sdio device
 * @conext: sdio device context
 * @hif_handle: pointer to sdio function
 *
 * Return: 0 for success and non-zero for failure
 */
static A_STATUS hif_sdio_remove(void *context, void *hif_handle)
{
	HIF_ENTER();

	if (!scn) {
		QDF_TRACE(QDF_MODULE_ID_HIF, QDF_TRACE_LEVEL_ERROR,
			  "Global SDIO context is NULL");
		return A_ERROR;
	}

	atomic_set(&hif_sdio_load_state, false);
	athdiag_procfs_remove();

#ifndef TARGET_DUMP_FOR_NON_QC_PLATFORM
	iounmap(scn->ramdump_base);
#endif

	if (scn) {
		qdf_mem_free(scn);
		scn = NULL;
	}

	HIF_EXIT();

	return 0;
}

/**
 * hif_sdio_suspend() - sdio suspend routine
 * @context: sdio device context
 *
 * Return: 0 for success and non-zero for failure
 */
static A_STATUS hif_sdio_suspend(void *context)
{
	return 0;
}

/**
 * hif_sdio_resume() - sdio resume routine
 * @context: sdio device context
 *
 * Return: 0 for success and non-zero for failure
 */
static A_STATUS hif_sdio_resume(void *context)
{
	return 0;
}

/**
 * hif_sdio_power_change() - change power state of sdio bus
 * @conext: sdio device context
 * @config: power state configurartion
 *
 * Return: 0 for success and non-zero for failure
 */
static A_STATUS hif_sdio_power_change(void *context, uint32_t config)
{
	return 0;
}

/*
 * Module glue.
 */
#include <linux/version.h>
static char *version = "HIF (Atheros/multi-bss)";
static char *dev_info = "ath_hif_sdio";

/**
 * init_ath_hif_sdio() - initialize hif sdio callbacks
 * @param: none
 *
 * Return: 0 for success and non-zero for failure
 */
static int init_ath_hif_sdio(void)
{
	static int probed;
	QDF_STATUS status;
	struct osdrv_callbacks osdrv_callbacks;
	HIF_ENTER();

	qdf_mem_zero(&osdrv_callbacks, sizeof(osdrv_callbacks));
	osdrv_callbacks.device_inserted_handler = hif_sdio_probe;
	osdrv_callbacks.device_removed_handler = hif_sdio_remove;
	osdrv_callbacks.device_suspend_handler = hif_sdio_suspend;
	osdrv_callbacks.device_resume_handler = hif_sdio_resume;
	osdrv_callbacks.device_power_change_handler = hif_sdio_power_change;

	if (probed)
		return -ENODEV;
	probed++;

	QDF_TRACE(QDF_MODULE_ID_HIF, QDF_TRACE_LEVEL_INFO, "%s %d", __func__,
		  __LINE__);
	status = hif_init(&osdrv_callbacks);
	if (status != QDF_STATUS_SUCCESS) {
		QDF_TRACE(QDF_MODULE_ID_HIF, QDF_TRACE_LEVEL_FATAL,
			  "%s hif_init failed!", __func__);
		return -ENODEV;
	}
	QDF_TRACE(QDF_MODULE_ID_HIF, QDF_TRACE_LEVEL_ERROR,
		 "%s: %s\n", dev_info, version);

	return 0;
}

/**
 * hif_targ_is_awake(): check if target is awake
 *
 * This function returns true if the target is awake
 *
 * @scn: struct hif_softc
 * @mem: mapped mem base
 *
 * Return: bool
 */
bool hif_targ_is_awake(struct hif_softc *scn, void *__iomem *mem)
{
	return true;
}

/**
 * hif_sdio_bus_suspend() - suspend the bus
 *
 * This function suspends the bus, but sdio doesn't need to suspend.
 * Therefore do nothing.
 *
 * Return: 0 for success and non-zero for failure
 */
int hif_sdio_bus_suspend(struct hif_softc *hif_ctx)
{
	struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx);
	struct hif_sdio_dev *hif_device = scn->hif_handle;
	struct device *dev = &hif_device->func->dev;

	hif_device_suspend(dev);
	return 0;
}


/**
 * hif_sdio_bus_resume() - hif resume API
 *
 * This function resumes the bus. but sdio doesn't need to resume.
 * Therefore do nothing.
 *
 * Return: 0 for success and non-zero for failure
 */
int hif_sdio_bus_resume(struct hif_softc *hif_ctx)
{
	struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx);
	struct hif_sdio_dev *hif_device = scn->hif_handle;
	struct device *dev = &hif_device->func->dev;

	hif_device_resume(dev);
	return 0;
}

/**
 * hif_enable_power_gating() - enable HW power gating
 *
 * Return: n/a
 */
void hif_enable_power_gating(void *hif_ctx)
{
}

/**
 * hif_disable_aspm() - hif_disable_aspm
 *
 * Return: n/a
 */
void hif_disable_aspm(void)
{
}

/**
 * hif_sdio_close() - hif_bus_close
 *
 * Return: None
 */
void hif_sdio_close(struct hif_softc *hif_sc)
{
}

/**
 * hif_sdio_open() - hif_bus_open
 * @hif_sc: hif context
 * @bus_type: bus type
 *
 * Return: QDF status
 */
QDF_STATUS hif_sdio_open(struct hif_softc *hif_sc,
				   enum qdf_bus_type bus_type)
{
	QDF_STATUS status;

	hif_sc->bus_type = bus_type;
	status = init_ath_hif_sdio();

	return status;
}

/**
 * hif_get_target_type() - Get the target type
 *
 * This function is used to query the target type.
 *
 * @ol_sc: ol_softc struct pointer
 * @dev: device pointer
 * @bdev: bus dev pointer
 * @bid: bus id pointer
 * @hif_type: HIF type such as HIF_TYPE_QCA6180
 * @target_type: target type such as TARGET_TYPE_QCA6180
 *
 * Return: 0 for success
 */
int hif_get_target_type(struct hif_softc *ol_sc, struct device *dev,
	void *bdev, const hif_bus_id *bid, uint32_t *hif_type,
	uint32_t *target_type)
{

	return 0;
}

void hif_get_target_revision(struct hif_softc *ol_sc)
{
	struct hif_softc *ol_sc_local = (struct hif_softc *)ol_sc;
	struct hif_opaque_softc *hif_hdl = GET_HIF_OPAQUE_HDL(ol_sc_local);
	uint32_t chip_id = 0;
	QDF_STATUS rv;

	rv = hif_diag_read_access(hif_hdl,
			(CHIP_ID_ADDRESS | RTC_SOC_BASE_ADDRESS), &chip_id);
	if (rv != QDF_STATUS_SUCCESS) {
		HIF_ERROR("%s[%d]: get chip id fail\n", __func__, __LINE__);
	} else {
		ol_sc_local->target_info.target_revision =
			CHIP_ID_REVISION_GET(chip_id);
	}
}

/**
 * hif_sdio_enable_bus() - hif_enable_bus
 * @hif_sc: hif context
 * @dev: dev
 * @bdev: bus dev
 * @bid: bus id
 * @type: bus type
 *
 * Return: QDF_STATUS
 */
QDF_STATUS hif_sdio_enable_bus(struct hif_softc *hif_sc,
			struct device *dev, void *bdev, const hif_bus_id *bid,
			enum hif_enable_type type)
{
	int ret = 0;
	const struct sdio_device_id *id = (const struct sdio_device_id *)bid;
	struct hif_sdio_softc *sc = HIF_GET_SDIO_SOFTC(hif_sc);

	init_waitqueue_head(&sync_wait_queue);
	if (hif_sdio_device_inserted(dev, id)) {
			HIF_ERROR("wlan: %s hif_sdio_device_inserted"
					  "failed", __func__);
		return QDF_STATUS_E_NOMEM;
	}

	wait_event_interruptible_timeout(sync_wait_queue,
			  atomic_read(&hif_sdio_load_state) == true,
			  HIF_SDIO_LOAD_TIMEOUT);
	hif_sc->hostdef = ol_sc->hostdef;
	hif_sc->targetdef = ol_sc->targetdef;
	hif_sc->bus_type = ol_sc->bus_type;
	hif_sc->target_info.target_type = ol_sc->target_info.target_type;

	sc->hif_handle = scn->hif_handle;
	sc->aps_osdev.device = scn->aps_osdev.device;
	sc->aps_osdev.bc.bc_bustype = scn->aps_osdev.bc.bc_bustype;
	sc->target_lock = scn->target_lock;
	sc->targetdef = scn->targetdef;
	sc->hostdef = scn->hostdef;
	sc->aps_osdev.bdev = scn->aps_osdev.bdev;
	sc->ramdump_size = scn->ramdump_size;
	sc->ramdump_base = scn->ramdump_base;

	return ret;
}


/**
 * hif_sdio_disable_bus() - sdio disable bus
 * @hif_sc: hif softc pointer
 *
 * Return: none
 */
void hif_sdio_disable_bus(struct hif_softc *hif_sc)
{
	struct hif_sdio_softc *sc = HIF_GET_SDIO_SOFTC(hif_sc);
	struct sdio_func *func = sc->aps_osdev.bdev;

	hif_sdio_device_removed(func);
}

/**
 * hif_sdio_get_config_item - sdio configure bus
 * @hif_sc: hif context
 * @opcode: configuration type
 * @config: configuration value to set
 * @config_len: configuration length
 *
 * Return: QDF_STATUS_SUCCESS for sucess
 */
QDF_STATUS hif_sdio_get_config_item(struct hif_softc *hif_sc,
		     int opcode, void *config, uint32_t config_len)
{
	struct hif_sdio_softc *sc = HIF_GET_SDIO_SOFTC(hif_sc);
	struct hif_sdio_dev *hif_device = sc->hif_handle;

	return hif_configure_device(hif_device,
				opcode, config, config_len);
}

/**
 * hif_sdio_set_mailbox_swap - set mailbox swap
 * @hif_sc: hif context
 *
 * Return: None
 */
void hif_sdio_set_mailbox_swap(struct hif_softc *hif_sc)
{
	struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_sc);
	struct hif_sdio_dev *hif_device = scn->hif_handle;
	hif_device->swap_mailbox = true;
	return;
}

/**
 * hif_sdio_claim_device - set mailbox swap
 * @hif_sc: hif context
 *
 * Return: None
 */
void hif_sdio_claim_device(struct hif_softc *hif_sc)
{
	struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_sc);
	struct hif_sdio_dev *hif_device = scn->hif_handle;
	hif_device->claimed_ctx = hif_sc;
	return;
}

/**
 * hif_sdio_mask_interrupt_call() - disbale hif device irq
 * @scn: pointr to softc structure
 *
 * Return: None
 */
void hif_sdio_mask_interrupt_call(struct hif_softc *scn)
{
	struct hif_sdio_softc *hif_ctx = HIF_GET_SDIO_SOFTC(scn);
	struct hif_sdio_dev *hif_device = hif_ctx->hif_handle;
	hif_mask_interrupt(hif_device);
	return;
}

/**
 * hif_trigger_dump() - trigger various dump cmd
 * @scn: struct hif_opaque_softc
 * @cmd_id: dump command id
 * @start: start/stop dump
 *
 * Return: None
 */
void hif_trigger_dump(struct hif_opaque_softc *scn, uint8_t cmd_id, bool start)
{
}

/**
 * hif_check_fw_reg() - hif_check_fw_reg
 * @scn: scn
 * @state:
 *
 * Return: int
 */
int hif_check_fw_reg(struct hif_opaque_softc *scn)
{
	return 0;
}

/**
 * hif_wlan_disable() - call the platform driver to disable wlan
 * @scn: scn
 *
 * Return: void
 */
void hif_wlan_disable(struct hif_softc *scn)
{
}

/**
 * hif_config_target() - configure hif bus
 * @hif_hdl: hif handle
 * @state:
 *
 * Return: int
 */
int hif_config_target(void *hif_hdl)
{
	return 0;
}
