/*
 * Copyright (c) 2018 The Linux Foundation. All rights reserved.
 *
 * 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.
 */

/**
 * DOC: init_cmd_api.c
 *
 * WMI Init command prepare & send APIs
 */
#include <qdf_status.h>
#include <qdf_types.h>
#include <wlan_objmgr_psoc_obj.h>
#include <wlan_objmgr_pdev_obj.h>
#include <target_if.h>
#include <service_ready_util.h>
#include <wlan_tgt_def_config.h>
#include <wlan_reg_ucfg_api.h>
#include <init_cmd_api.h>
#include <wlan_defs.h>
#include <target_if_scan.h>

/**
 *  init_deinit_alloc_host_mem_chunk() - allocates chunk of memory requested
 *                                       by FW.
 *  @psoc: PSOC object
 *  @tgt_hdl: Target PSOC info
 *  @req_id: request id
 *  @idx: chunk id
 *  @num_units: Number of units
 *  @unit_len: Unit length
 *  @num_unit_info: Num unit info
 *
 *  API to allocate host memory chunk requested by FW
 *
 *  Return: num_units on successful allocation
 *          0 on failure
 */
static uint32_t init_deinit_alloc_host_mem_chunk(struct wlan_objmgr_psoc *psoc,
			struct target_psoc_info *tgt_hdl,
			u_int32_t req_id, u_int32_t idx, u_int32_t num_units,
			u_int32_t unit_len, u_int32_t num_unit_info)
{
	qdf_dma_addr_t paddr;
	uint32_t ichunk = 0;
	struct tgt_info *info;
	qdf_device_t qdf_dev;

	info = (&tgt_hdl->info);

	if (!num_units  || !unit_len)
		return 0;

	qdf_dev = wlan_psoc_get_qdf_dev(psoc);
	/*
	 * We have skip smaller chunks memory allocation for TXBF_CV buffer
	 * as Firmware is expecting continuous memory
	 */
	if (!((num_unit_info & HOST_CONTIGUOUS_MEM_CHUNK_REQUIRED) &&
	      (req_id == TXBF_CV_POOL0 || req_id == TXBF_CV_POOL1 ||
	      req_id == TXBF_CV_POOL2))) {
		ichunk = ((num_units * unit_len) >>
			HOST_MEM_CHUNK_MAX_SIZE_POWER2);
		if (ichunk)
			num_units = num_units / (ichunk + 1);
	}

	info->mem_chunks[idx].vaddr = NULL;
	/* reduce the requested allocation by half until allocation succeeds */
	while (!info->mem_chunks[idx].vaddr && num_units) {
		info->mem_chunks[idx].vaddr = qdf_mem_alloc_consistent(qdf_dev,
				qdf_dev->dev, num_units * unit_len, &paddr);
		if (info->mem_chunks[idx].vaddr == NULL) {
			if (num_unit_info &
					HOST_CONTIGUOUS_MEM_CHUNK_REQUIRED) {
				num_units = 0;
				target_if_err("mem chink alloc failed for %d",
					      idx);
				break;
			}
			/* reduce length by half */
			num_units = (num_units >> 1);
		} else {
			info->mem_chunks[idx].paddr = paddr;
			info->mem_chunks[idx].len = num_units*unit_len;
			info->mem_chunks[idx].req_id =  req_id;
		}
	}
	target_if_info(
		"req_id %d idx %d num_units %d unit_len %d",
		req_id, idx, num_units, unit_len);

	return num_units;
}

/* Host mem size units, it is used for round-off */
#define HOST_MEM_SIZE_UNIT 4

/**
 *  init_deinit_alloc_host_mem() - allocates amount of memory requested by FW.
 *  @psoc: PSOC object
 *  @tgt_hdl: Target PSOC info
 *  @req_id: request id
 *  @num_units: Number of units
 *  @unit_len: Unit length
 *  @num_unit_info: Num unit info
 *
 *  API to allocate host memory requested by FW
 *
 *  Return: QDF_STATUS_SUCCESS on successful allocation
 *          QDF_STATUS_E_FAILURE on failure
 */
static QDF_STATUS init_deinit_alloc_host_mem(struct wlan_objmgr_psoc *psoc,
		struct target_psoc_info *tgt_hdl, u_int32_t req_id,
		u_int32_t num_units, u_int32_t unit_len,
		u_int32_t num_unit_info)
{
	struct tgt_info *info;
	uint32_t remaining_units;
	uint32_t allocated_units = 0;
	uint32_t idx;

	info = (&tgt_hdl->info);
	/* adjust the length to nearest multiple of unit size */
	unit_len = (unit_len + (HOST_MEM_SIZE_UNIT - 1)) &
				(~(HOST_MEM_SIZE_UNIT - 1));
	idx = info->num_mem_chunks;
	remaining_units = num_units;

	while (remaining_units) {
		if (idx == MAX_MEM_CHUNKS) {
			target_if_err(
				"REACHED MAX CHUNK LIMIT for mem units %d",
					num_units);
			target_if_err(
			"unit len %d requested by FW, only allocated %d",
				unit_len, (num_units - remaining_units));
			info->num_mem_chunks = idx;
			return QDF_STATUS_E_FAILURE;
		}

		if ((tgt_hdl->tif_ops) &&
		    (tgt_hdl->tif_ops->mem_mgr_alloc_chunk))
			allocated_units = tgt_hdl->tif_ops->mem_mgr_alloc_chunk(
						psoc, tgt_hdl, req_id, idx,
						remaining_units,
						unit_len, num_unit_info);
		else
			allocated_units = init_deinit_alloc_host_mem_chunk(
						psoc, tgt_hdl, req_id, idx,
						remaining_units,
						unit_len, num_unit_info);
		if (allocated_units == 0) {
			target_if_err("FAILED TO ALLOC mem unit len %d",
				      unit_len);
			target_if_err("units requested %d units allocated %d",
				      num_units, (num_units - remaining_units));
			info->num_mem_chunks = idx;
			return QDF_STATUS_E_NOMEM;
		}
		remaining_units -= allocated_units;
		++idx;
	}
	info->num_mem_chunks = idx;

	return QDF_STATUS_SUCCESS;
}

/**
 *  init_deinit_alloc_num_units() - allocates num units requested by FW.
 *  @psoc: PSOC object
 *  @tgt_hdl: Target PSOC info
 *  @mem_reqs: pointer to mem req
 *  @num_units: Number
 *  @i: FW priority
 *  @idx: Index
 *
 *  API to allocate num units of host memory requested by FW
 *
 *  Return: QDF_STATUS_SUCCESS on successful allocation
 *          QDF_STATUS_E_FAILURE on failure
 */
static QDF_STATUS init_deinit_alloc_num_units(struct wlan_objmgr_psoc *psoc,
			struct target_psoc_info *tgt_hdl,
			host_mem_req *mem_reqs, uint16_t fw_prio,
			uint16_t idx)
{
	struct tgt_info *info;
	uint32_t num_units;
	QDF_STATUS status;

	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return QDF_STATUS_E_INVAL;
	}

	info = (&tgt_hdl->info);

	if (((fw_prio == FW_MEM_HIGH_PRIORITY) &&
	     (mem_reqs[idx].num_unit_info &
			HOST_CONTIGUOUS_MEM_CHUNK_REQUIRED)) ||
	    ((fw_prio == FW_MEM_LOW_PRIORITY) &&
			(!(mem_reqs[idx].num_unit_info &
				HOST_CONTIGUOUS_MEM_CHUNK_REQUIRED)))) {
		/* First allocate the memory that requires contiguous memory */
		num_units = mem_reqs[idx].num_units;
		if (mem_reqs[idx].num_unit_info) {
			if (mem_reqs[idx].num_unit_info &
					NUM_UNITS_IS_NUM_PEERS) {
				/*
				 * number of units allocated is equal to number
				 * of peers, 1 extra for self peer on target.
				 * this needs to be fixed, host and target can
				 * get out of sync
				 */
				num_units = info->wlan_res_cfg.num_peers + 1;
			}
			if (mem_reqs[idx].num_unit_info &
				NUM_UNITS_IS_NUM_ACTIVE_PEERS) {
				/*
				 * Requesting allocation of memory using
				 * num_active_peers in qcache. if qcache is
				 * disabled in host, then it should allocate
				 * memory for num_peers instead of
				 * num_active_peers.
				 */
				if (info->wlan_res_cfg.num_active_peers)
					num_units =
					info->wlan_res_cfg.num_active_peers + 1;
				else
					num_units =
					info->wlan_res_cfg.num_peers + 1;
			}
		}

		target_if_info("idx %d req %d  num_units %d num_unit_info %d unit size %d actual units %d",
			       idx, mem_reqs[idx].req_id,
			       mem_reqs[idx].num_units,
			       mem_reqs[idx].num_unit_info,
			       mem_reqs[idx].unit_size, num_units);

		status = init_deinit_alloc_host_mem(psoc, tgt_hdl,
				mem_reqs[idx].req_id, num_units,
				mem_reqs[idx].unit_size,
				mem_reqs[idx].num_unit_info);
		if (status == QDF_STATUS_E_FAILURE) {
			target_if_err(
				"psoc:(%pK) num_mem_chunk exceeds supp number",
									psoc);
			return QDF_STATUS_E_FAILURE;
		} else if (status == QDF_STATUS_E_NOMEM) {
			target_if_err("soc:(%pK) mem alloc failure", psoc);
			return QDF_STATUS_E_NOMEM;
		}
	}

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS init_deinit_free_num_units(struct wlan_objmgr_psoc *psoc,
			struct target_psoc_info *tgt_hdl)
{
	struct tgt_info *info;
	qdf_device_t qdf_dev;
	uint32_t idx;
	QDF_STATUS status;

	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return QDF_STATUS_E_INVAL;
	}

	if ((tgt_hdl->tif_ops) &&
	    (tgt_hdl->tif_ops->mem_mgr_free_chunks)) {
		status = tgt_hdl->tif_ops->mem_mgr_free_chunks(psoc, tgt_hdl);
	} else {
		qdf_dev = wlan_psoc_get_qdf_dev(psoc);
		info = (&tgt_hdl->info);
		for (idx = 0; idx < info->num_mem_chunks; idx++) {
			qdf_mem_free_consistent(
				qdf_dev, qdf_dev->dev,
				info->mem_chunks[idx].len,
				info->mem_chunks[idx].vaddr,
				info->mem_chunks[idx].paddr,
				qdf_get_dma_mem_context(
					(&(info->mem_chunks[idx])), memctx));

			info->mem_chunks[idx].vaddr = NULL;
			info->mem_chunks[idx].paddr = 0;
			info->mem_chunks[idx].len = 0;
		}
		info->num_mem_chunks = 0;
		status = QDF_STATUS_SUCCESS;
	}

	return status;
}

QDF_STATUS init_deinit_handle_host_mem_req(
		 struct wlan_objmgr_psoc *psoc,
		 struct target_psoc_info *tgt_hdl, uint8_t *event)
{
	uint8_t num_mem_reqs;
	host_mem_req *mem_reqs;
	uint32_t i;
	uint32_t idx;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	void *wmi_handle;
	struct tgt_info *info;

	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return QDF_STATUS_E_INVAL;
	}

	wmi_handle = target_psoc_get_wmi_hdl(tgt_hdl);
	info = (&tgt_hdl->info);

	mem_reqs = wmi_extract_host_mem_req_from_service_ready(wmi_handle,
							event, &num_mem_reqs);
	if (!num_mem_reqs)
		return QDF_STATUS_SUCCESS;

	for (i = 0; i < FW_PRIORITY_MAX; i++) {
		for (idx = 0; idx < num_mem_reqs; idx++) {
			status = init_deinit_alloc_num_units(psoc, tgt_hdl,
				mem_reqs, i, idx);
			if (status != QDF_STATUS_SUCCESS)
				return status;
		}
	}

	return status;
}

void init_deinit_derive_band_to_mac_param(
		struct wlan_objmgr_psoc *psoc,
		struct target_psoc_info *tgt_hdl,
		struct wmi_host_pdev_band_to_mac *band_to_mac)
{
	uint8_t i;
	struct wlan_psoc_host_mac_phy_caps *mac_phy_cap;
	struct wlan_psoc_host_hal_reg_capabilities_ext *reg_cap;
	struct tgt_info *info;

	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null ");
		return;
	}

	info = (&tgt_hdl->info);

	reg_cap = ucfg_reg_get_hal_reg_cap(psoc);
	if (!reg_cap) {
		target_if_err("reg cap is NULL");
		return;
	}

	for (i = 0; i < target_psoc_get_num_radios(tgt_hdl); i++) {
		mac_phy_cap = &info->mac_phy_cap[i];
		if (mac_phy_cap->supported_bands ==
			(WMI_HOST_WLAN_5G_CAPABILITY |
					WMI_HOST_WLAN_2G_CAPABILITY)) {
			/*Supports both 5G and 2G. Use freq from both radios*/
			target_if_info("Supports both 2G and 5G");
			band_to_mac[i].pdev_id = mac_phy_cap->pdev_id;
			band_to_mac[i].start_freq =
					reg_cap[i].low_2ghz_chan;
			band_to_mac[i].end_freq =
					reg_cap[i].high_5ghz_chan;

		} else if (mac_phy_cap->supported_bands ==
				WMI_HOST_WLAN_2G_CAPABILITY) {
			band_to_mac[i].pdev_id = mac_phy_cap->pdev_id;
			band_to_mac[i].start_freq =
					reg_cap[i].low_2ghz_chan;
			band_to_mac[i].end_freq =
					reg_cap[i].high_2ghz_chan;

			reg_cap[mac_phy_cap->phy_id].low_5ghz_chan = 0;
			reg_cap[mac_phy_cap->phy_id].high_5ghz_chan = 0;

			target_if_info(
			"2G radio - pdev_id = %d start_freq = %d end_freq= %d",
				band_to_mac[i].pdev_id,
				band_to_mac[i].start_freq,
				band_to_mac[i].end_freq);

		} else if (mac_phy_cap->supported_bands ==
					WMI_HOST_WLAN_5G_CAPABILITY) {
			band_to_mac[i].pdev_id = mac_phy_cap->pdev_id;
			band_to_mac[i].start_freq =
						reg_cap[i].low_5ghz_chan;
			band_to_mac[i].end_freq =
						reg_cap[i].high_5ghz_chan;

			reg_cap[mac_phy_cap->phy_id].low_2ghz_chan = 0;
			reg_cap[mac_phy_cap->phy_id].high_2ghz_chan = 0;

			target_if_info(
			"5G radio -pdev_id = %d start_freq = %d end_freq =%d\n",
				band_to_mac[i].pdev_id,
				band_to_mac[i].start_freq,
				band_to_mac[i].end_freq);
		}
	}
}

void init_deinit_prepare_send_init_cmd(
		 struct wlan_objmgr_psoc *psoc,
		 struct target_psoc_info *tgt_hdl)
{
	struct wmi_init_cmd_param init_param = {0};
	struct tgt_info *info;
	void *wmi_handle;
	QDF_STATUS ret_val;

	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return;
	}

	wmi_handle = target_psoc_get_wmi_hdl(tgt_hdl);
	info = (&tgt_hdl->info);

	init_param.res_cfg = &info->wlan_res_cfg;
	init_param.num_mem_chunks = info->num_mem_chunks;
	init_param.mem_chunks = info->mem_chunks;

	if (init_deinit_is_service_ext_msg(psoc, tgt_hdl) ==
			QDF_STATUS_SUCCESS) {
		init_param.hw_mode_id = info->preferred_hw_mode;
		/* Temp change, until FW submits support for handling this TLV
		 * For single mode, skip sending hw_mode
		 */
		if (info->preferred_hw_mode == WMI_HOST_HW_MODE_SINGLE)
			init_param.hw_mode_id = WMI_HOST_HW_MODE_MAX;

		init_param.num_band_to_mac = target_psoc_get_num_radios(
								tgt_hdl);

		init_deinit_derive_band_to_mac_param(psoc, tgt_hdl,
						     init_param.band_to_mac);
	}

	ret_val = target_if_alloc_pdevs(psoc, tgt_hdl);
	if (ret_val != QDF_STATUS_SUCCESS)
		return;

	ret_val = target_if_update_pdev_tgt_info(psoc, tgt_hdl);
	if (ret_val != QDF_STATUS_SUCCESS)
		return;

	target_if_info("FW version 0x%x ", info->target_caps.fw_version);
	if (init_deinit_is_service_ext_msg(psoc, tgt_hdl) ==
			QDF_STATUS_SUCCESS)
		target_if_info("0x%x\n",
			       info->service_ext_param.fw_build_vers_ext);
	else
		target_if_info("0x%x\n", info->target_caps.fw_version_1);

	wmi_unified_init_cmd_send(wmi_handle, &init_param);

	/* Set Max scans allowed */
	target_if_scan_set_max_active_scans(psoc,
					    WLAN_MAX_ACTIVE_SCANS_ALLOWED);
}
