// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2005-2014, 2018-2025 Intel Corporation
 * Copyright (C) 2015-2017 Intel Deutschland GmbH
 */
#include "iwl-trans.h"
#include "iwl-op-mode.h"
#include "fw/img.h"
#include "iwl-csr.h"

#include "xvt.h"
#include "fw/dbg.h"
#include "fw/testmode.h"
#include "fw/api/power.h"
#include "fw/pnvm.h"

#define XVT_UCODE_ALIVE_TIMEOUT	HZ

struct iwl_xvt_alive_data {
	__le32 sku_id[3];
	bool valid;
};

static int iwl_xvt_send_dqa_cmd(struct iwl_xvt *xvt)
{
	struct iwl_dqa_enable_cmd dqa_cmd = {
		.cmd_queue = cpu_to_le32(IWL_MVM_DQA_CMD_QUEUE),
	};
	u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, DQA_ENABLE_CMD);
	int ret;

	ret = iwl_xvt_send_cmd_pdu(xvt, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd);
	if (ret)
		IWL_ERR(xvt, "Failed to send DQA enabling command: %d\n", ret);
	else
		IWL_DEBUG_FW(xvt, "Working in DQA mode\n");

	return ret;
}

static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait,
			 struct iwl_rx_packet *pkt,
			 void *data)
{
	struct iwl_xvt *xvt =
		container_of(notif_wait, struct iwl_xvt, notif_wait);
	struct iwl_xvt_alive_data *alive_data = data;
	struct xvt_alive_resp_ver2 *palive2;
	struct iwl_alive_ntf_v3 *palive3;
	struct iwl_alive_ntf_v7 *palive7;
	struct iwl_alive_ntf *palive8;
	struct iwl_lmac_alive *lmac1, *lmac2;
	struct iwl_umac_alive *umac;
	u32 rx_packet_payload_size = iwl_rx_packet_payload_len(pkt);
	u16 status, flags;
	u32 lmac_error_event_table, umac_error_event_table;
	u32 version = iwl_fw_lookup_notif_ver(xvt->fw, LEGACY_GROUP,
					      UCODE_ALIVE_NTFY, 0);

	if (rx_packet_payload_size == sizeof(*palive2)) {

		palive2 = (void *)pkt->data;

		lmac_error_event_table =
			le32_to_cpu(palive2->error_event_table_ptr);

		alive_data->valid = le16_to_cpu(palive2->status) ==
				    IWL_ALIVE_STATUS_OK;
		iwl_tm_set_fw_ver(xvt->trans, palive2->ucode_major,
				  palive2->ucode_minor);
		umac_error_event_table =
			le32_to_cpu(palive2->error_info_addr);

		IWL_DEBUG_FW(xvt,
			     "Alive VER2 ucode status 0x%04x revision 0x%01X "
			     "0x%01X flags 0x%01X\n",
			     le16_to_cpu(palive2->status), palive2->ver_type,
			     palive2->ver_subtype, palive2->flags);

		IWL_DEBUG_FW(xvt,
			     "UMAC version: Major - 0x%x, Minor - 0x%x\n",
			     palive2->umac_major, palive2->umac_minor);
	} else {
		if (rx_packet_payload_size == sizeof(*palive3)) {
			palive3 = (void *)pkt->data;
			status = le16_to_cpu(palive3->status);
			flags = le16_to_cpu(palive3->flags);
			lmac1 = &palive3->lmac_data;
			umac = &palive3->umac_data;

			IWL_DEBUG_FW(xvt, "Alive VER3\n");
		} else if (version == 6 || version == 7) {
			/* v5-7 are compatible (only IMR/flags addition) */
			__le32 lmac2_err_ptr;

			palive7 = (void *)pkt->data;
			status = le16_to_cpu(palive7->status);
			flags = le16_to_cpu(palive7->flags);
			lmac1 = &palive7->lmac_data[0];
			lmac2 = &palive7->lmac_data[1];
			umac = &palive7->umac_data;
			lmac2_err_ptr = lmac2->dbg_ptrs.error_event_table_ptr;
			xvt->trans->dbg.lmac_error_event_table[1] =
				le32_to_cpu(lmac2_err_ptr);

			BUILD_BUG_ON(sizeof(alive_data->sku_id) !=
				     sizeof(palive7->sku_id.data));
			memcpy(alive_data->sku_id, palive7->sku_id.data,
			       sizeof(palive7->sku_id.data));

			IWL_DEBUG_FW(xvt,
				     "Alive VER%d - Got sku_id: 0x0%x 0x0%x 0x0%x\n",
				     version,
				     le32_to_cpu(alive_data->sku_id[0]),
				     le32_to_cpu(alive_data->sku_id[1]),
				     le32_to_cpu(alive_data->sku_id[2]));
		} else if (rx_packet_payload_size == sizeof(*palive8)) {
			/* v8 compatible (only platform_id addition) */
			__le32 lmac2_err_ptr;

			palive8 = (void *)pkt->data;
			status = le16_to_cpu(palive8->status);
			flags = le16_to_cpu(palive8->flags);
			lmac1 = &palive8->lmac_data[0];
			lmac2 = &palive8->lmac_data[1];
			umac = &palive8->umac_data;
			lmac2_err_ptr = lmac2->dbg_ptrs.error_event_table_ptr;
			xvt->trans->dbg.lmac_error_event_table[1] =
				le32_to_cpu(lmac2_err_ptr);

			BUILD_BUG_ON(sizeof(alive_data->sku_id) !=
				     sizeof(palive8->sku_id.data));
			memcpy(alive_data->sku_id, palive8->sku_id.data,
			       sizeof(palive8->sku_id.data));

			IWL_DEBUG_FW(xvt,
				     "Alive VER%d - Got sku_id: 0x0%x 0x0%x 0x0%x\n",
				     version,
				     le32_to_cpu(alive_data->sku_id[0]),
				     le32_to_cpu(alive_data->sku_id[1]),
				     le32_to_cpu(alive_data->sku_id[2]));
		} else {
			IWL_ERR(xvt, "unrecognized alive notificatio\n");
			return false;
		}

		alive_data->valid = status == IWL_ALIVE_STATUS_OK;
		lmac_error_event_table =
			le32_to_cpu(lmac1->dbg_ptrs.error_event_table_ptr);
		iwl_tm_set_fw_ver(xvt->trans, le32_to_cpu(lmac1->ucode_major),
				  le32_to_cpu(lmac1->ucode_minor));
		umac_error_event_table =
			le32_to_cpu(umac->dbg_ptrs.error_info_addr);

		IWL_DEBUG_FW(xvt,
			     "status 0x%04x rev 0x%01X 0x%01X flags 0x%01X\n",
			     status, lmac1->ver_type, lmac1->ver_subtype,
			     flags);
		IWL_DEBUG_FW(xvt,
			     "UMAC version: Major - 0x%x, Minor - 0x%x\n",
			     umac->umac_major, umac->umac_minor);
	}

	iwl_fw_lmac1_set_alive_err_table(xvt->trans, lmac_error_event_table);
	if (umac_error_event_table)
		iwl_fw_umac_set_alive_err_table(xvt->trans,
						umac_error_event_table);

	return true;
}

static int iwl_xvt_load_ucode_wait_alive(struct iwl_xvt *xvt,
					 enum iwl_ucode_type ucode_type)
{
	struct iwl_notification_wait alive_wait;
	struct iwl_xvt_alive_data alive_data = {};
	int ret;
	enum iwl_ucode_type old_type = xvt->fwrt.cur_fw_img;
	static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY };
	struct iwl_scd_txq_cfg_cmd cmd = {
				.scd_queue = IWL_XVT_DEFAULT_TX_QUEUE,
				.action = SCD_CFG_ENABLE_QUEUE,
				.window = IWL_FRAME_LIMIT,
				.sta_id = IWL_XVT_TX_STA_ID_DEFAULT,
				.ssn = 0,
				.tx_fifo = IWL_XVT_DEFAULT_TX_FIFO,
				.aggregate = false,
				.tid = IWL_MAX_TID_COUNT,
			};

	iwl_fw_set_current_image(&xvt->fwrt, ucode_type);

	if (xvt->sw_stack_cfg.fw_dbg_flags & ~IWL_XVT_DBG_FLAGS_NO_DEFAULT_TXQ)
		return -EOPNOTSUPP;

	iwl_init_notification_wait(&xvt->notif_wait, &alive_wait,
				   alive_cmd, ARRAY_SIZE(alive_cmd),
				   iwl_alive_fn, &alive_data);

	ret = iwl_trans_start_fw(xvt->trans, xvt->fw, ucode_type,
				 ucode_type == IWL_UCODE_INIT);
	if (ret) {
		iwl_fw_set_current_image(&xvt->fwrt, old_type);
		iwl_remove_notification(&xvt->notif_wait, &alive_wait);
		return ret;
	}

	/*
	 * Some things may run in the background now, but we
	 * just wait for the ALIVE notification here.
	 */
	ret = iwl_wait_notification(&xvt->notif_wait, &alive_wait,
				    XVT_UCODE_ALIVE_TIMEOUT);
	if (ret) {
		IWL_ERR(xvt, "XVT: ret:%d\n", ret);
		iwl_fw_set_current_image(&xvt->fwrt, old_type);
		return ret;
	}

	if (!alive_data.valid) {
		IWL_ERR(xvt, "Loaded ucode is not valid!\n");
		iwl_fw_set_current_image(&xvt->fwrt, old_type);
		return -EIO;
	}

	/* fresh firmware was loaded */
	xvt->fw_error = false;

	iwl_trans_fw_alive(xvt->trans);

	ret = iwl_xvt_pnvm_load(xvt->trans, &xvt->notif_wait,
				xvt->fw, alive_data.sku_id);
	if (ret) {
		IWL_ERR(xvt, "Timeout waiting for PNVM load!\n");
		iwl_fw_set_current_image(&xvt->fwrt, old_type);
		return ret;
	}

	ret = iwl_init_paging(&xvt->fwrt, ucode_type);
	if (ret)
		return ret;

	if (ucode_type == IWL_UCODE_REGULAR &&
	    fw_has_capa(&xvt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_DQA_SUPPORT)) {
		ret = iwl_xvt_send_dqa_cmd(xvt);
		if (ret)
			return ret;
	}
	/*
	 * Starting from 22000 tx queue allocation must be done after add
	 * station, so it is not part of the init flow.
	 */
	if (!iwl_xvt_is_unified_fw(xvt) &&
	    iwl_xvt_has_default_txq(xvt) &&
	    ucode_type != IWL_UCODE_INIT) {
		iwl_trans_txq_enable_cfg(xvt->trans, IWL_XVT_DEFAULT_TX_QUEUE,
					 0, NULL, 0);

		WARN(iwl_xvt_send_cmd_pdu(xvt, SCD_QUEUE_CFG, 0, sizeof(cmd),
					  &cmd),
		     "Failed to configure queue %d on FIFO %d\n",
		     IWL_XVT_DEFAULT_TX_QUEUE, IWL_XVT_DEFAULT_TX_FIFO);
		xvt->tx_meta_data[XVT_LMAC_0_ID].queue =
					IWL_XVT_DEFAULT_TX_QUEUE;
	}

	xvt->fw_running = true;
#ifdef CPTCFG_IWLWIFI_DEBUGFS
	iwl_fw_set_dbg_rec_on(&xvt->fwrt);
#endif

	return 0;
}

static int iwl_xvt_send_extended_config(struct iwl_xvt *xvt)
{
	/*
	 * TODO: once WRT will be implemented in xVT, IWL_INIT_DEBUG_CFG
	 * flag will not always be set
	 */
	struct iwl_init_extended_cfg_cmd ext_cfg = {
		.init_flags = cpu_to_le32(BIT(IWL_INIT_NVM) |
					  BIT(IWL_INIT_DEBUG_CFG)),

	};

	if (xvt->sw_stack_cfg.load_mask & IWL_XVT_LOAD_MASK_RUNTIME)
		ext_cfg.init_flags |= cpu_to_le32(BIT(IWL_INIT_PHY));

	return iwl_xvt_send_cmd_pdu(xvt, WIDE_ID(SYSTEM_GROUP,
						 INIT_EXTENDED_CFG_CMD), 0,
				    sizeof(ext_cfg), &ext_cfg);
}

static int iwl_xvt_config_ltr(struct iwl_xvt *xvt)
{
	struct iwl_ltr_config_cmd cmd = {
		.flags = cpu_to_le32(LTR_CFG_FLAG_FEATURE_ENABLE),
	};

	if (!iwl_trans_is_ltr_enabled(xvt->trans))
		return 0;

	return iwl_xvt_send_cmd_pdu(xvt, LTR_CONFIG, 0, sizeof(cmd), &cmd);
}

int iwl_xvt_run_fw(struct iwl_xvt *xvt, u32 ucode_type)
{
	int ret;

	if (ucode_type >= IWL_UCODE_TYPE_MAX)
		return -EINVAL;

	lockdep_assert_held(&xvt->mutex);

	if (xvt->state != IWL_XVT_STATE_UNINITIALIZED) {
		if (xvt->fw_running) {
			xvt->fw_running = false;
			if (xvt->fwrt.cur_fw_img == IWL_UCODE_REGULAR)
				iwl_xvt_txq_disable(xvt);
		}
		iwl_fw_dbg_stop_sync(&xvt->fwrt);
		iwl_trans_stop_device(xvt->trans);
	}

	ret = iwl_trans_start_hw(xvt->trans);
	if (ret) {
		IWL_ERR(xvt, "Failed to start HW\n");
		return ret;
	}

	iwl_trans_set_bits_mask(xvt->trans,
				CSR_HW_IF_CONFIG_REG,
				CSR_HW_IF_CONFIG_REG_BIT_MAC_SI,
				CSR_HW_IF_CONFIG_REG_BIT_MAC_SI);

	iwl_dbg_tlv_time_point(&xvt->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL);

	/* Will also start the device */
	ret = iwl_xvt_load_ucode_wait_alive(xvt, ucode_type);
	if (ret) {
		IWL_ERR(xvt, "Failed to start ucode: %d\n", ret);
		iwl_fw_dbg_stop_sync(&xvt->fwrt);
		iwl_trans_stop_device(xvt->trans);
		return ret;
	}

	iwl_dbg_tlv_time_point(&xvt->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE,
			       NULL);

	iwl_fw_disable_dbg_asserts(&xvt->fwrt);
	iwl_get_shared_mem_conf(&xvt->fwrt);

	if (iwl_xvt_is_unified_fw(xvt)) {
		ret = iwl_xvt_send_extended_config(xvt);
		if (ret) {
			IWL_ERR(xvt, "Failed to send extended_config: %d\n",
				ret);
			iwl_fw_dbg_stop_sync(&xvt->fwrt);
			iwl_trans_stop_device(xvt->trans);
			return ret;
		}
	}

	if (xvt->fwrt.cur_fw_img == IWL_UCODE_REGULAR &&
	    (!fw_has_capa(&xvt->fw->ucode_capa,
			  IWL_UCODE_TLV_CAPA_SET_LTR_GEN2)))
		WARN_ON(iwl_xvt_config_ltr(xvt));

	if (!iwl_trans_dbg_ini_valid(xvt->trans)) {
		xvt->fwrt.dump.conf = FW_DBG_INVALID;
		/* if we have a destination, assume EARLY START */
		if (xvt->fw->dbg.dest_tlv)
			xvt->fwrt.dump.conf = FW_DBG_START_FROM_ALIVE;
		iwl_fw_start_dbg_conf(&xvt->fwrt, FW_DBG_START_FROM_ALIVE);
	}

	return ret;
}
