/*
 * Copyright (c) 2013-2016 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 <linux/usb.h>
#include <linux/usb/hcd.h>
#include "if_usb.h"
#include "hif_usb_internal.h"
#include "bmi_msg.h"		/* TARGET_TYPE_ */
#include "regtable_usb.h"
#include "ol_fw.h"
#include "hif_debug.h"
#include "epping_main.h"
#include "hif_main.h"
#include "qwlan_version.h"

#define DELAY_FOR_TARGET_READY 200	/* 200ms */

/* Save memory addresses where we save FW ram dump, and then we could obtain
 * them by symbol table.
 */
uint32_t fw_stack_addr;
void *fw_ram_seg_addr[FW_RAM_SEG_CNT];



static int hif_usb_unload_dev_num = -1;
struct hif_usb_softc *g_usb_sc = NULL;

void hif_usb_device_deinit(struct hif_usb_softc *sc);
QDF_STATUS hif_usb_device_init(struct hif_usb_softc *sc);

/**
 * hif_usb_diag_write_cold_reset() - reset SOC by sending a diag command
 * @scn: pointer to ol_softc structure
 *
 * Return: QDF_STATUS_SUCCESS if success else an appropriate QDF_STATUS error
 */
static inline QDF_STATUS
hif_usb_diag_write_cold_reset(struct hif_softc *scn)
{
	struct hif_opaque_softc *hif_hdl = GET_HIF_OPAQUE_HDL(scn);


	HIF_DBG("%s: resetting SOC", __func__);

	return hif_diag_write_access(hif_hdl,
				(ROME_USB_SOC_RESET_CONTROL_COLD_RST_LSB |
				ROME_USB_RTC_SOC_BASE_ADDRESS),
				SOC_RESET_CONTROL_COLD_RST_SET(1));
}

/**
 * hif_usb_procfs_init() - create init procfs
 * @scn: pointer to hif_usb_softc structure
 *
 * Return: int 0 if success else an appropriate error number
 */
static int
hif_usb_procfs_init(struct hif_softc *scn)
{
	int ret = 0;

	HIF_ENTER();

	if (athdiag_procfs_init(scn) != 0) {
		HIF_ERROR("athdiag_procfs_init failed");
		ret = A_ERROR;
	}

	scn->athdiag_procfs_inited = true;

	HIF_EXIT();
	return ret;
}

/**
 * hif_nointrs(): disable IRQ
 * @scn: pointer to struct hif_softc
 *
 * This function stops interrupt(s)
 *
 * Return: none
 */
void hif_usb_nointrs(struct hif_softc *scn)
{

}

/**
 * hif_usb_reboot() - called at reboot time to reset WLAN SOC
 * @nb: pointer to notifier_block registered during register_reboot_notifier
 * @val: code indicating reboot reason
 * @v: unused pointer
 *
 * Return: int 0 if success else an appropriate error number
 */
static int hif_usb_reboot(struct notifier_block *nb, unsigned long val,
				void *v)
{
	struct hif_usb_softc *sc;

	HIF_ENTER();
	sc = container_of(nb, struct hif_usb_softc, reboot_notifier);
	/* do cold reset */
	hif_usb_diag_write_cold_reset(HIF_GET_SOFTC(sc));
	HIF_EXIT();
	return NOTIFY_DONE;
}

/**
 * hif_usb_disable_lpm() - Disable lpm feature of usb2.0
 * @udev: pointer to usb_device for which LPM is to be disabled
 *
 * LPM needs to be disabled to avoid usb2.0 probe timeout
 *
 * Return: int 0 if success else an appropriate error number
 */
static int hif_usb_disable_lpm(struct usb_device *udev)
{
	struct usb_hcd *hcd;
	int ret = -EPERM;

	HIF_ENTER();

	if (!udev || !udev->bus) {
		HIF_ERROR("Invalid input parameters");
		goto exit;
	}

	hcd = bus_to_hcd(udev->bus);
	if (udev->usb2_hw_lpm_enabled) {
		if (hcd->driver->set_usb2_hw_lpm) {
			ret = hcd->driver->set_usb2_hw_lpm(hcd, udev, false);
			if (!ret) {
				udev->usb2_hw_lpm_enabled = false;
				udev->usb2_hw_lpm_capable = false;
				HIF_TRACE("%s: LPM is disabled", __func__);
			} else {
				HIF_TRACE("%s: Fail to disable LPM",
						__func__);
			}
		} else {
			HIF_TRACE("%s: hcd doesn't support LPM",
						__func__);
		}
	} else {
		HIF_TRACE("%s: LPM isn't enabled", __func__);
	}
exit:
	HIF_EXIT();
	return ret;
}

/**
 * hif_usb_enable_bus() - enable usb bus
 * @ol_sc: hif_softc struct
 * @dev: device pointer
 * @bdev: bus dev pointer
 * @bid: bus id pointer
 * @type: enum hif_enable_type such as HIF_ENABLE_TYPE_PROBE
 *
 * Return: QDF_STATUS_SUCCESS on success and error QDF status on failure
 */
QDF_STATUS hif_usb_enable_bus(struct hif_softc *scn,
			struct device *dev, void *bdev,
			const hif_bus_id *bid,
			enum hif_enable_type type)

{
	struct usb_interface *interface = (struct usb_interface *)bdev;
	struct usb_device_id *id = (struct usb_device_id *)bid;
	int ret = 0;
	struct hif_usb_softc *sc;
	struct usb_device *usbdev = interface_to_usbdev(interface);
	int vendor_id, product_id;

	usb_get_dev(usbdev);

	if (!scn) {
		HIF_ERROR("%s: hif_ctx is NULL", __func__);
		goto err_usb;
	}

	sc = HIF_GET_USB_SOFTC(scn);

	HIF_INFO("%s hif_softc %p usbdev %p interface %p\n",
		__func__,
		scn,
		usbdev,
		interface);

	vendor_id = qdf_le16_to_cpu(usbdev->descriptor.idVendor);
	product_id = qdf_le16_to_cpu(usbdev->descriptor.idProduct);

	HIF_ERROR("%s: con_mode = 0x%x, vendor_id = 0x%x product_id = 0x%x",
		__func__, hif_get_conparam(scn), vendor_id, product_id);

	sc->pdev = (void *)usbdev;
	sc->dev = &usbdev->dev;
	sc->devid = id->idProduct;

	if ((usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
			USB_REQ_SET_CONFIGURATION, 0, 1, 0, NULL, 0,
			HZ)) < 0) {
		HIF_ERROR("%s[%d]", __func__, __LINE__);
		goto err_usb;
	}

	usb_set_interface(usbdev, 0, 0);
	/* disable lpm to avoid usb2.0 probe timeout */
	hif_usb_disable_lpm(usbdev);

	/* params need to be added - TO DO
	scn->enableuartprint = 1;
	scn->enablefwlog = 0;
	scn->max_no_of_peers = 1; */

	sc->interface = interface;
	sc->reboot_notifier.notifier_call = hif_usb_reboot;
	register_reboot_notifier(&sc->reboot_notifier);

	if (hif_usb_device_init(sc) != QDF_STATUS_SUCCESS) {
		HIF_ERROR("ath: %s: hif_usb_device_init failed", __func__);
		goto err_reset;
	}

	if (hif_usb_procfs_init(scn))
		goto err_reset;

	hif_usb_unload_dev_num = usbdev->devnum;
	g_usb_sc = sc;
	HIF_EXIT();
	return 0;

err_reset:
	hif_usb_diag_write_cold_reset(scn);
	g_usb_sc = NULL;
	hif_usb_unload_dev_num = -1;
	unregister_reboot_notifier(&sc->reboot_notifier);
err_usb:
	ret = QDF_STATUS_E_FAILURE;
	usb_put_dev(usbdev);
	return ret;
}


/**
 * hif_usb_close(): close bus, delete hif_sc
 * @ol_sc: soft_sc struct
 *
 * Return: none
 */
void hif_usb_close(struct hif_softc *scn)
{
	g_usb_sc = NULL;
}

/**
 * hif_usb_disable_bus(): This function disables usb bus
 * @hif_ctx: pointer to struct hif_softc
 *
 * Return: none
 */
void hif_usb_disable_bus(struct hif_softc *hif_ctx)
{
	struct hif_usb_softc *sc = HIF_GET_USB_SOFTC(hif_ctx);
	struct usb_interface *interface = sc->interface;
	struct usb_device *udev = interface_to_usbdev(interface);

	HIF_TRACE("%s: trying to remove hif_usb!", __func__);

	/* disable lpm to avoid following cold reset will
	 * cause xHCI U1/U2 timeout
	 */
	usb_disable_lpm(udev);

	/* wait for disable lpm */
	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(msecs_to_jiffies(DELAY_FOR_TARGET_READY));
	set_current_state(TASK_RUNNING);

	/* do cold reset */
	hif_usb_diag_write_cold_reset(hif_ctx);

	if (g_usb_sc->suspend_state)
		hif_bus_resume(GET_HIF_OPAQUE_HDL(hif_ctx));

	unregister_reboot_notifier(&sc->reboot_notifier);
	usb_put_dev(interface_to_usbdev(interface));

	hif_usb_device_deinit(sc);

	HIF_TRACE("%s hif_usb removed !!!!!!", __func__);
}

/**
 * hif_usb_bus_suspend() - suspend the bus
 * @hif_ctx: hif_ctx
 *
 * This function suspends the bus, but usb doesn't need to suspend.
 * Therefore just remove all the pending urb transactions
 *
 * Return: 0 for success and non-zero for failure
 */
int hif_usb_bus_suspend(struct hif_softc *hif_ctx)
{
	struct hif_usb_softc *sc = HIF_GET_USB_SOFTC(hif_ctx);
	HIF_DEVICE_USB *device = HIF_GET_USB_DEVICE(hif_ctx);

	HIF_ENTER();
	sc->suspend_state = 1;
	usb_hif_flush_all(device);
	HIF_EXIT();
	return 0;
}

/**
 * hif_usb_bus_resume() - hif resume API
 * @hif_ctx: struct hif_opaque_softc
 *
 * This function resumes the bus. but usb doesn't need to resume.
 * Post recv urbs for RX data pipe
 *
 * Return: 0 for success and non-zero for failure
 */
int hif_usb_bus_resume(struct hif_softc *hif_ctx)
{
	struct hif_usb_softc *sc = HIF_GET_USB_SOFTC(hif_ctx);
	HIF_DEVICE_USB *device = HIF_GET_USB_DEVICE(hif_ctx);

	HIF_ENTER();
	sc->suspend_state = 0;
	usb_hif_start_recv_pipes(device);

	HIF_EXIT();
	return 0;
}

/**
 * hif_usb_bus_reset_resume() - resume the bus after reset
 * @scn: struct hif_opaque_softc
 *
 * This function is called to tell the driver that USB device has been resumed
 * and it has also been reset. The driver should redo any necessary
 * initialization. This function resets WLAN SOC.
 *
 * Return: int 0 for success, non zero for failure
 */
int hif_usb_bus_reset_resume(struct hif_softc *hif_ctx)
{
	int ret = 0;
	HIF_ENTER();
	if (hif_usb_diag_write_cold_reset(hif_ctx) != QDF_STATUS_SUCCESS)
		ret = 1;

	HIF_EXIT();
	return ret;
}

/**
 * hif_usb_open()- initialization routine for usb bus
 * @ol_sc: ol_sc
 * @bus_type: bus type
 *
 * Return: QDF_STATUS_SUCCESS on success and error QDF status on failure
 */
QDF_STATUS hif_usb_open(struct hif_softc *hif_ctx,
		enum qdf_bus_type bus_type)
{
	hif_ctx->bus_type = bus_type;
	return QDF_STATUS_SUCCESS;
}

/**
 * hif_usb_disable_isr(): disable isr
 * @hif_ctx: struct hif_softc
 *
 * Return: void
 */
void hif_usb_disable_isr(struct hif_softc *hif_ctx)
{
	/* TODO */
}

/**
 * hif_usb_reg_tbl_attach()- attach hif, target register tables
 * @scn: pointer to ol_softc structure
 *
 * Attach host and target register tables based on target_type, target_version
 *
 * Return: none
 */
void hif_usb_reg_tbl_attach(struct hif_softc *scn)
{
	u_int32_t hif_type, target_type;
	int32_t ret = 0;
	uint32_t chip_id;
	QDF_STATUS rv;
	struct hif_target_info *tgt_info = &scn->target_info;
	struct hif_opaque_softc *hif_hdl = GET_HIF_OPAQUE_HDL(scn);

	if (scn->hostdef == NULL && scn->targetdef == NULL) {
		switch (tgt_info->target_type) {
		case TARGET_TYPE_AR6320:
			switch (tgt_info->target_version) {
			case AR6320_REV1_VERSION:
			case AR6320_REV1_1_VERSION:
			case AR6320_REV1_3_VERSION:
				hif_type = HIF_TYPE_AR6320;
				target_type = TARGET_TYPE_AR6320;
				break;
			case AR6320_REV2_1_VERSION:
			case AR6320_REV3_VERSION:
			case QCA9377_REV1_1_VERSION:
			case QCA9379_REV1_VERSION:
				hif_type = HIF_TYPE_AR6320V2;
				target_type = TARGET_TYPE_AR6320V2;
				break;
			default:
				ret = -1;
				break;
			}
			break;
		default:
			ret = -1;
			break;
		}

		if (ret)
			return;

		/* assign target register table if we find
		corresponding type */
		hif_register_tbl_attach(scn, hif_type);
		target_register_tbl_attach(scn, target_type);
		/* read the chip revision*/
		rv = hif_diag_read_access(hif_hdl,
					(CHIP_ID_ADDRESS |
					RTC_SOC_BASE_ADDRESS),
					&chip_id);
		if (rv != QDF_STATUS_SUCCESS) {
			HIF_ERROR("%s: get chip id val (%d)", __func__,
				rv);
		}
		tgt_info->target_revision =
				CHIP_ID_REVISION_GET(chip_id);
	}
}

/**
 * hif_usb_get_hw_info()- attach register table for USB
 * @hif_ctx: pointer to hif_softc structure

 * This function is used to attach the host and target register tables.
 * Ideally, we should not attach register tables as a part of this function.
 * There is scope of cleanup to move register table attach during
 * initialization for USB bus.
 *
 * The reason we are doing register table attach for USB here is that, it relies
 * on target_info->target_type and target_info->target_version,
 * which get populated during bmi_firmware_download. "hif_get_fw_info" is the
 * only initialization related call into HIF there after.
 *
 * To fix this, we can move the "get target info, functionality currently in
 * bmi_firmware_download into hif initialization functions. This change will
 * affect all buses. Can be taken up as a part of convergence.
 *
 * Return: none
 */
void hif_usb_get_hw_info(struct hif_softc *hif_ctx)
{
	hif_usb_reg_tbl_attach(hif_ctx);
}


/**
 * hif_bus_configure() - configure the bus
 * @scn: pointer to the hif context.
 *
 * return: 0 for success. nonzero for failure.
 */
int hif_usb_bus_configure(struct hif_softc *scn)
{
	return 0;
}

/**
 * hif_usb_irq_enable() - hif_usb_irq_enable
 * @scn: hif_softc
 * @ce_id: ce_id
 *
 * Return: void
 */
void hif_usb_irq_enable(struct hif_softc *scn, int ce_id)
{
}

/**
 * hif_usb_irq_disable() - hif_usb_irq_disable
 * @scn: hif_softc
 * @ce_id: ce_id
 *
 * Return: void
 */
void hif_usb_irq_disable(struct hif_softc *scn, int ce_id)
{
}

/**
 * hif_usb_shutdown_bus_device() - This function shuts down the device
 * @scn: hif opaque pointer
 *
 * Return: void
 */
void hif_usb_shutdown_bus_device(struct hif_softc *scn)
{
}

/**
 * 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_wlan_disable() - call the platform driver to disable wlan
 * @scn: scn
 *
 * Return: void
 */
void hif_wlan_disable(struct hif_softc *scn)
{
}

/**
 * hif_fw_assert_ramdump_pattern() - handle firmware assert with ramdump pattern
 * @sc: pointer to hif_usb_softc structure
 *
 * Return: void
 */

void hif_fw_assert_ramdump_pattern(struct hif_usb_softc *sc)
{
	uint32_t *reg, pattern, i = 0;
	uint32_t len;
	uint8_t *data;
	uint8_t *ram_ptr = NULL;
	char *fw_ram_seg_name[FW_RAM_SEG_CNT] = {"DRAM", "IRAM", "AXI"};
	size_t fw_ram_reg_size[FW_RAM_SEG_CNT] = {
				  FW_RAMDUMP_DRAMSIZE,
				  FW_RAMDUMP_IRAMSIZE,
				  FW_RAMDUMP_AXISIZE };

	data = sc->fw_data;
	len = sc->fw_data_len;
	pattern = *((A_UINT32 *) data);

	qdf_assert(sc->ramdump_index < FW_RAM_SEG_CNT);
	i = sc->ramdump_index;
	reg = (uint32_t *) (data + 4);
	if (sc->fw_ram_dumping == 0) {
		sc->fw_ram_dumping = 1;
		HIF_ERROR("Firmware %s dump:\n", fw_ram_seg_name[i]);
		sc->ramdump[i] =
			qdf_mem_malloc(sizeof(struct fw_ramdump) +
					fw_ram_reg_size[i]);
		if (!sc->ramdump[i]) {
			pr_err("Fail to allocate memory for ram dump");
			QDF_BUG(0);
		}
		(sc->ramdump[i])->mem = (uint8_t *) (sc->ramdump[i] + 1);
		fw_ram_seg_addr[i] = (sc->ramdump[i])->mem;
		HIF_ERROR("FW %s start addr = %#08x\n",
			fw_ram_seg_name[i], *reg);
		HIF_ERROR("Memory addr for %s = %p\n",
			fw_ram_seg_name[i],
			(sc->ramdump[i])->mem);
		(sc->ramdump[i])->start_addr = *reg;
		(sc->ramdump[i])->length = 0;
	}
	reg++;
	ram_ptr = (sc->ramdump[i])->mem + (sc->ramdump[i])->length;
	(sc->ramdump[i])->length += (len - 8);
	if (sc->ramdump[i]->length <= fw_ram_reg_size[i]) {
		qdf_mem_copy(ram_ptr, (A_UINT8 *) reg, len - 8);
	} else {
		HIF_ERROR("memory copy overlap\n");
		QDF_BUG(0);
	}

	if (pattern == FW_RAMDUMP_END_PATTERN) {
		HIF_ERROR("%s memory size = %d\n", fw_ram_seg_name[i],
				(sc->ramdump[i])->length);
		if (i == (FW_RAM_SEG_CNT - 1))
			QDF_BUG(0);

		sc->ramdump_index++;
		sc->fw_ram_dumping = 0;
	}
}

/**
 * hif_usb_ramdump_handler(): dump bus debug registers
 * @scn: struct hif_opaque_softc
 *
 * This function is to receive information of firmware crash dump, and
 * save it in host memory. It consists of 5 parts: registers, call stack,
 * DRAM dump, IRAM dump, and AXI dump, and they are reported to host in order.
 *
 * registers: wrapped in a USB packet by starting as FW_ASSERT_PATTERN and
 *            60 registers.
 * call stack: wrapped in multiple USB packets, and each of them starts as
 *             FW_REG_PATTERN and contains multiple double-words. The tail
 *             of the last packet is FW_REG_END_PATTERN.
 * DRAM dump: wrapped in multiple USB pakcets, and each of them start as
 *            FW_RAMDUMP_PATTERN and contains multiple double-wors. The tail
 *            of the last packet is FW_RAMDUMP_END_PATTERN;
 * IRAM dump and AXI dump are with the same format as DRAM dump.
 *
 * Return: 0 for success or error code
 */

void hif_usb_ramdump_handler(struct hif_opaque_softc *scn)
{
	uint32_t *reg, pattern, i, start_addr = 0;
	uint32_t len;
	uint8_t *data;
	uint8_t str_buf[128];
	uint32_t remaining;
	struct hif_usb_softc *sc = HIF_GET_USB_SOFTC(scn);
	struct hif_softc *hif_ctx = HIF_GET_SOFTC(scn);
	struct hif_target_info *tgt_info = &hif_ctx->target_info;

	data = sc->fw_data;
	len = sc->fw_data_len;
	pattern = *((A_UINT32 *) data);

	if (pattern == FW_ASSERT_PATTERN) {
		HIF_ERROR("Firmware crash detected...\n");
		HIF_ERROR("Host SW version: %s\n", QWLAN_VERSIONSTR);
		HIF_ERROR("target_type: %d.target_version %d. target_revision%d.",
			tgt_info->target_type,
			tgt_info->target_version,
			tgt_info->target_revision);

		reg = (uint32_t *) (data + 4);
		print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET, 16, 4, reg,
				min_t(A_UINT32, len - 4, FW_REG_DUMP_CNT * 4),
				false);
		sc->fw_ram_dumping = 0;

	} else if (pattern == FW_REG_PATTERN) {
		reg = (uint32_t *) (data + 4);
		start_addr = *reg++;
		if (sc->fw_ram_dumping == 0) {
			pr_err("Firmware stack dump:");
			sc->fw_ram_dumping = 1;
			fw_stack_addr = start_addr;
		}
		remaining = len - 8;
		/* len is in byte, but it's printed in double-word. */
		for (i = 0; i < (len - 8); i += 16) {
			if ((*reg == FW_REG_END_PATTERN) && (i == len - 12)) {
				sc->fw_ram_dumping = 0;
				pr_err("Stack start address = %#08x\n",
					fw_stack_addr);
				break;
			}
			hex_dump_to_buffer(reg, remaining, 16, 4, str_buf,
						sizeof(str_buf), false);
			pr_err("%#08x: %s\n", start_addr + i, str_buf);
			remaining -= 16;
			reg += 4;
		}
	} else if ((!sc->enable_self_recovery) &&
			((pattern & FW_RAMDUMP_PATTERN_MASK) ==
						FW_RAMDUMP_PATTERN)) {
		hif_fw_assert_ramdump_pattern(sc);
	}
}

#ifndef QCA_WIFI_3_0
/**
 * 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;
}
#endif

