/*
 * 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_event_handler.c
 *
 * WMI common event handler implementation source file
 */

#include <qdf_status.h>
#include <wlan_objmgr_psoc_obj.h>
#include <wlan_objmgr_pdev_obj.h>
#include <target_if.h>
#include <target_if_reg.h>
#include <init_event_handler.h>
#include <service_ready_util.h>
#include <service_ready_param.h>
#include <init_cmd_api.h>

static int init_deinit_service_ready_event_handler(ol_scn_t scn_handle,
							uint8_t *event,
							uint32_t data_len)
{
	int err_code;
	struct wlan_objmgr_psoc *psoc;
	struct target_psoc_info *tgt_hdl;
	wmi_legacy_service_ready_callback legacy_callback;
	void *wmi_handle;
	QDF_STATUS ret_val;

	if (!scn_handle) {
		target_if_err("scn handle NULL in service ready handler");
		return -EINVAL;
	}

	psoc = target_if_get_psoc_from_scn_hdl(scn_handle);
	if (!psoc) {
		target_if_err("psoc is null in service ready handler");
		return -EINVAL;
	}

	tgt_hdl = (struct target_psoc_info *)wlan_psoc_get_tgt_if_handle(
						psoc);
	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null in service ready ev");
		return -EINVAL;
	}

	ret_val = target_if_sw_version_check(psoc, tgt_hdl, event);

	wmi_handle = target_psoc_get_wmi_hdl(tgt_hdl);

	err_code = init_deinit_populate_service_bitmap(wmi_handle, event,
			tgt_hdl->info.service_bitmap);
	if (err_code)
		goto exit;

	err_code = init_deinit_populate_fw_version_cmd(wmi_handle, event);
	if (err_code)
		goto exit;

	err_code = init_deinit_populate_target_cap(wmi_handle, event,
				   &(tgt_hdl->info.target_caps));
	if (err_code)
		goto exit;

	err_code = init_deinit_populate_phy_reg_cap(psoc, wmi_handle, event,
				    &(tgt_hdl->info), true);
	if (err_code)
		goto exit;

	if (init_deinit_validate_160_80p80_fw_caps(psoc, tgt_hdl) !=
			QDF_STATUS_SUCCESS) {
		wlan_psoc_nif_op_flag_set(psoc, WLAN_SOC_OP_VHT_INVALID_CAP);
	}

	target_if_ext_res_cfg_enable(psoc, tgt_hdl, event);

	if (wmi_service_enabled(wmi_handle, wmi_service_tt))
		wlan_psoc_nif_fw_ext_cap_set(psoc, WLAN_SOC_CEXT_TT_SUPPORT);

	if (wmi_service_enabled(wmi_handle, wmi_service_widebw_scan))
		wlan_psoc_nif_fw_ext_cap_set(psoc, WLAN_SOC_CEXT_WIDEBAND_SCAN);

	if (wmi_service_enabled(wmi_handle, wmi_service_check_cal_version))
		wlan_psoc_nif_fw_ext_cap_set(psoc, WLAN_SOC_CEXT_SW_CAL);

	target_if_info(" TT support %d, Wide BW Scan %d, SW cal %d",
		wlan_psoc_nif_fw_ext_cap_get(psoc, WLAN_SOC_CEXT_TT_SUPPORT),
		wlan_psoc_nif_fw_ext_cap_get(psoc, WLAN_SOC_CEXT_WIDEBAND_SCAN),
		wlan_psoc_nif_fw_ext_cap_get(psoc, WLAN_SOC_CEXT_SW_CAL));

	target_if_mesh_support_enable(psoc, tgt_hdl, event);

	target_if_smart_antenna_enable(psoc, tgt_hdl, event);

	target_if_peer_cfg_enable(psoc, tgt_hdl, event);

	target_if_atf_cfg_enable(psoc, tgt_hdl, event);

	target_if_qwrap_cfg_enable(psoc, tgt_hdl, event);

	target_if_lteu_cfg_enable(psoc, tgt_hdl, event);

	/* override derived value, if it exceeds max peer count */
	if ((wlan_psoc_get_max_peer_count(psoc) >
		tgt_hdl->info.wlan_res_cfg.num_active_peers) &&
		(wlan_psoc_get_max_peer_count(psoc) <
			(tgt_hdl->info.wlan_res_cfg.num_peers -
				tgt_hdl->info.wlan_res_cfg.num_vdevs))) {
		tgt_hdl->info.wlan_res_cfg.num_peers =
				wlan_psoc_get_max_peer_count(psoc) +
					tgt_hdl->info.wlan_res_cfg.num_vdevs;
	}
	legacy_callback = target_if_get_psoc_legacy_service_ready_cb();
	if (!legacy_callback) {
		err_code = -EINVAL;
		goto exit;
	}

	err_code = legacy_callback(wmi_service_ready_event_id,
				  scn_handle, event, data_len);
	init_deinit_chainmask_config(psoc, tgt_hdl);

	if (wmi_service_enabled(wmi_handle, wmi_service_mgmt_tx_wmi)) {
		wlan_psoc_nif_fw_ext_cap_set(psoc, WLAN_SOC_CEXT_WMI_MGMT_REF);
		target_if_info("WMI mgmt service enabled");
	} else {
		wlan_psoc_nif_fw_ext_cap_clear(psoc,
					       WLAN_SOC_CEXT_WMI_MGMT_REF);
		target_if_info("WMI mgmt service disabled");
	}

	err_code = init_deinit_handle_host_mem_req(psoc, tgt_hdl, event);
	if (err_code != QDF_STATUS_SUCCESS)
		goto exit;

	target_if_reg_set_offloaded_info(psoc);
	if (!wmi_service_enabled(wmi_handle, wmi_service_ext_msg)) {
		target_if_info("No EXT message, send init command");
		tgt_hdl->info.wmi_service_ready = TRUE;
		target_psoc_set_num_radios(tgt_hdl, 1);
		/* send init command */
		init_deinit_prepare_send_init_cmd(psoc, tgt_hdl);
	} else {
		target_if_info("Wait for EXT message");
	}

exit:
	return err_code;
}

static int init_deinit_service_ext_ready_event_handler(ol_scn_t scn_handle,
						uint8_t *event,
						uint32_t data_len)
{
	int err_code;
	struct wlan_objmgr_psoc *psoc;
	struct target_psoc_info *tgt_hdl;
	void *wmi_handle;
	struct tgt_info *info;
	wmi_legacy_service_ready_callback legacy_callback;

	if (!scn_handle) {
		target_if_err("scn handle NULL in service ready handler");
		return -EINVAL;
	}

	psoc = target_if_get_psoc_from_scn_hdl(scn_handle);
	if (!psoc) {
		target_if_err("psoc is null in service ready handler");
		return -EINVAL;
	}

	tgt_hdl = (struct target_psoc_info *)wlan_psoc_get_tgt_if_handle(
						psoc);
	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null in service ready ev");
		return -EINVAL;
	}

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

	err_code = init_deinit_populate_service_ready_ext_param(wmi_handle,
				event, &(info->service_ext_param));
	if (err_code)
		goto exit;

	target_psoc_set_num_radios(tgt_hdl, 0);
	err_code =  init_deinit_populate_hw_mode_capability(wmi_handle,
					    event, tgt_hdl);
	if (err_code)
		goto exit;

	if (init_deinit_is_preferred_hw_mode_supported(psoc, tgt_hdl)
			== FALSE)
		return -EINVAL;

	target_if_print_service_ready_ext_param(psoc, tgt_hdl);

	err_code = init_deinit_populate_phy_reg_cap(psoc, wmi_handle,
					   event, info, false);
	if (err_code)
		goto exit;

	target_if_add_11ax_modes(psoc, tgt_hdl);

	if (init_deinit_chainmask_table_alloc(
				&(info->service_ext_param)) ==
							QDF_STATUS_SUCCESS) {
		err_code = init_deinit_populate_chainmask_tables(wmi_handle,
				event,
				&(info->service_ext_param.chainmask_table[0]));
		if (err_code)
			goto exit;
	}

	err_code = init_deinit_populate_dbr_ring_cap(psoc, wmi_handle,
						event, info);
	if (err_code)
		goto exit;

	legacy_callback = target_if_get_psoc_legacy_service_ready_cb();
	if (legacy_callback)
		legacy_callback(wmi_service_ready_ext_event_id,
				scn_handle, event, data_len);

	info->wlan_res_cfg.num_vdevs = (target_psoc_get_num_radios(tgt_hdl) *
					info->wlan_res_cfg.num_vdevs);
	info->wlan_res_cfg.beacon_tx_offload_max_vdev =
				(target_psoc_get_num_radios(tgt_hdl) *
				info->wlan_res_cfg.beacon_tx_offload_max_vdev);

	info->wmi_service_ready = TRUE;

	init_deinit_prepare_send_init_cmd(psoc, tgt_hdl);

exit:
	return err_code;
}

static int init_deinit_service_available_handler(ol_scn_t scn_handle,
						uint8_t *event,
						uint32_t data_len)
{
	struct wlan_objmgr_psoc *psoc;
	struct target_psoc_info *tgt_hdl;
	void *wmi_handle;

	if (!scn_handle) {
		target_if_err("scn handle NULL");
		return -EINVAL;
	}

	psoc = target_if_get_psoc_from_scn_hdl(scn_handle);
	if (!psoc) {
		target_if_err("psoc is null");
		return -EINVAL;
	}

	tgt_hdl = (struct target_psoc_info *)wlan_psoc_get_tgt_if_handle(
						psoc);
	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return -EINVAL;
	}

	wmi_handle = target_psoc_get_wmi_hdl(tgt_hdl);

	if (wmi_save_ext_service_bitmap(wmi_handle, event, NULL) !=
					QDF_STATUS_SUCCESS) {
		target_if_err("Failed to save ext service bitmap");
		return -EINVAL;
	}

	return 0;
}

/* MAC address fourth byte index */
#define MAC_BYTE_4 4

static int init_deinit_ready_event_handler(ol_scn_t scn_handle,
						uint8_t *event,
						uint32_t data_len)
{
	struct wlan_objmgr_psoc *psoc;
	struct wlan_objmgr_pdev *pdev;
	struct target_psoc_info *tgt_hdl;
	void *wmi_handle;
	struct wmi_host_fw_abi_ver fw_ver;
	uint8_t myaddr[QDF_MAC_ADDR_SIZE];
	struct tgt_info *info;
	struct wmi_host_ready_ev_param ready_ev;
	wmi_legacy_service_ready_callback legacy_callback;
	uint8_t num_radios, i;

	if (!scn_handle) {
		target_if_err("scn handle NULL");
		return -EINVAL;
	}

	psoc = target_if_get_psoc_from_scn_hdl(scn_handle);
	if (!psoc) {
		target_if_err("psoc is null");
		return -EINVAL;
	}

	tgt_hdl = (struct target_psoc_info *)wlan_psoc_get_tgt_if_handle(
						psoc);
	if (!tgt_hdl) {
		target_if_err("target_psoc_info is null");
		return -EINVAL;
	}

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

	if (wmi_extract_fw_abi_version(wmi_handle, event, &fw_ver) ==
				QDF_STATUS_SUCCESS) {
		info->version.wlan_ver = fw_ver.sw_version;
		info->version.wlan_ver = fw_ver.abi_version;
	}

	if (wmi_check_and_update_fw_version(wmi_handle, event) < 0) {
		target_if_err("Version mismatch with FW");
		return -EINVAL;
	}

	if (wmi_extract_ready_event_params(wmi_handle, event, &ready_ev) !=
				QDF_STATUS_SUCCESS) {
		target_if_err("Failed to extract ready event");
		return -EINVAL;
	}

	if ((ready_ev.num_total_peer != 0) &&
	    (info->wlan_res_cfg.num_peers != ready_ev.num_total_peer)) {
		/* FW allocated number of peers is different than host
		 * requested. Update host max with FW reported value.
		 */
		target_if_info("Host Requested %d peers. FW Supports %d peers",
			       info->wlan_res_cfg.num_peers,
			       ready_ev.num_total_peer);
		info->wlan_res_cfg.num_peers = ready_ev.num_total_peer;
	}

	/* Indicate to the waiting thread that the ready
	 * event was received
	 */
	info->wlan_init_status = wmi_ready_extract_init_status(
						wmi_handle, event);

	legacy_callback = target_if_get_psoc_legacy_service_ready_cb();
	if (legacy_callback)
		legacy_callback(wmi_ready_event_id,
				scn_handle, event, data_len);

	num_radios = target_psoc_get_num_radios(tgt_hdl);
	/*
	 * For non-legacy HW, MAC addr list is extracted.
	 */
	if (num_radios > 1) {
		uint8_t num_mac_addr;
		wmi_host_mac_addr *addr_list;
		int i;

		addr_list = wmi_ready_extract_mac_addr_list(wmi_handle, event,
							    &num_mac_addr);
		if ((num_mac_addr >= num_radios) && (addr_list)) {
			for (i = 0; i < num_radios; i++) {
				WMI_HOST_MAC_ADDR_TO_CHAR_ARRAY(&addr_list[i],
								myaddr);
				pdev = wlan_objmgr_get_pdev_by_id(psoc, i,
								  WLAN_INIT_DEINIT_ID);
				if (!pdev) {
					target_if_err(" PDEV %d is NULL", i);
					return -EINVAL;
				}
				wlan_pdev_set_hw_macaddr(pdev, myaddr);
				wlan_objmgr_pdev_release_ref(pdev,
							WLAN_INIT_DEINIT_ID);

				/* assign 1st radio addr to psoc */
				if (i == 0)
					wlan_psoc_set_hw_macaddr(psoc, myaddr);
			}
			goto out;
		} else {
			target_if_err("Using default MAC addr for all radios..");
		}
	}

	/*
	 * We extract single MAC address in two scenarios:
	 * 1. In non-legacy case, if addr list is NULL or num_mac_addr < num_radios
	 * 2. In all legacy cases
	 */
	for (i = 0; i < num_radios; i++) {
		wmi_ready_extract_mac_addr(wmi_handle, event, myaddr);
		myaddr[MAC_BYTE_4] += i;
		pdev = wlan_objmgr_get_pdev_by_id(psoc, i, WLAN_INIT_DEINIT_ID);
		if (!pdev) {
			target_if_err(" PDEV %d is NULL", i);
			return -EINVAL;
		}
		wlan_pdev_set_hw_macaddr(pdev, myaddr);
		wlan_objmgr_pdev_release_ref(pdev, WLAN_INIT_DEINIT_ID);
		/* assign 1st radio addr to psoc */
		if (i == 0)
			wlan_psoc_set_hw_macaddr(psoc, myaddr);
	}

out:
	target_if_btcoex_cfg_enable(psoc, tgt_hdl, event);
	tgt_hdl->info.wmi_ready = TRUE;
	init_deinit_wakeup_host_wait(psoc, tgt_hdl);

	return 0;
}


QDF_STATUS init_deinit_register_tgt_psoc_ev_handlers(
				struct wlan_objmgr_psoc *psoc)
{
	struct target_psoc_info *tgt_hdl;
	void *wmi_handle;
	QDF_STATUS retval = QDF_STATUS_SUCCESS;

	if (!psoc) {
		target_if_err("psoc is null in register wmi handler");
		return QDF_STATUS_E_FAILURE;
	}

	tgt_hdl = (struct target_psoc_info *)wlan_psoc_get_tgt_if_handle(
						psoc);
	if (!tgt_hdl) {
		target_if_err("target_psoc_info null in register wmi hadler");
		return QDF_STATUS_E_FAILURE;
	}

	wmi_handle = target_psoc_get_wmi_hdl(tgt_hdl);

	retval = wmi_unified_register_event_handler(wmi_handle,
				wmi_service_ready_event_id,
				init_deinit_service_ready_event_handler,
				WMI_RX_WORK_CTX);
	retval = wmi_unified_register_event_handler(wmi_handle,
				wmi_service_ready_ext_event_id,
				init_deinit_service_ext_ready_event_handler,
				WMI_RX_WORK_CTX);
	retval = wmi_unified_register_event_handler(wmi_handle,
				wmi_service_available_event_id,
				init_deinit_service_available_handler,
				WMI_RX_UMAC_CTX);
	retval = wmi_unified_register_event_handler(wmi_handle,
				wmi_ready_event_id,
				init_deinit_ready_event_handler,
				WMI_RX_WORK_CTX);

	return retval;
}

