/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright (c) 2023 Corigine, Inc.
 * All rights reserved.
 */

#include "nfp_common.h"

#include "nfp_common_log.h"

/*
 * This is used by the reconfig protocol. It sets the maximum time waiting in
 * milliseconds before a reconfig timeout happens.
 */
#define NFP_NET_POLL_TIMEOUT    5000

int
nfp_reconfig_real(struct nfp_hw *hw,
		uint32_t update)
{
	uint32_t cnt;
	uint32_t new;
	struct timespec wait;

	PMD_DRV_LOG(DEBUG, "Writing to the configuration queue (%p)...",
			hw->qcp_cfg);

	if (hw->qcp_cfg == NULL) {
		PMD_DRV_LOG(ERR, "Bad configuration queue pointer.");
		return -ENXIO;
	}

	nfp_qcp_ptr_add(hw->qcp_cfg, NFP_QCP_WRITE_PTR, 1);

	wait.tv_sec = 0;
	wait.tv_nsec = 1000000; /* 1ms */

	PMD_DRV_LOG(DEBUG, "Polling for update ack...");

	/* Poll update field, waiting for NFP to ack the config */
	for (cnt = 0; ; cnt++) {
		new = nn_cfg_readl(hw, NFP_NET_CFG_UPDATE);
		if (new == 0)
			break;

		if ((new & NFP_NET_CFG_UPDATE_ERR) != 0) {
			PMD_DRV_LOG(ERR, "Reconfig error: %#08x.", new);
			return -1;
		}

		if (cnt >= NFP_NET_POLL_TIMEOUT) {
			PMD_DRV_LOG(ERR, "Reconfig timeout for %#08x after %u ms.",
					update, cnt);
			return -EIO;
		}

		nanosleep(&wait, 0); /* waiting for a 1ms */
	}

	PMD_DRV_LOG(DEBUG, "Ack DONE.");
	return 0;
}

/**
 * Reconfigure the NIC.
 *
 * Write the update word to the BAR and ping the reconfig queue. Then poll
 * until the firmware has acknowledged the update by zeroing the update word.
 *
 * @param hw
 *   Device to reconfigure.
 * @param ctrl
 *   The value for the ctrl field in the BAR config.
 * @param update
 *   The value for the update field in the BAR config.
 *
 * @return
 *   - (0) if OK to reconfigure the device.
 *   - (-EIO) if I/O err and fail to reconfigure the device.
 */
int
nfp_reconfig(struct nfp_hw *hw,
		uint32_t ctrl,
		uint32_t update)
{
	int ret;

	rte_spinlock_lock(&hw->reconfig_lock);

	nn_cfg_writel(hw, NFP_NET_CFG_CTRL, ctrl);
	nn_cfg_writel(hw, NFP_NET_CFG_UPDATE, update);

	rte_wmb();

	ret = nfp_reconfig_real(hw, update);

	rte_spinlock_unlock(&hw->reconfig_lock);

	if (ret != 0) {
		PMD_DRV_LOG(ERR, "Error NFP reconfig: ctrl=%#08x update=%#08x.",
				ctrl, update);
		return -EIO;
	}

	return 0;
}

/**
 * Reconfigure the NIC for the extend ctrl BAR.
 *
 * Write the update word to the BAR and ping the reconfig queue. Then poll
 * until the firmware has acknowledged the update by zeroing the update word.
 *
 * @param hw
 *   Device to reconfigure.
 * @param ctrl_ext
 *   The value for the first word of extend ctrl field in the BAR config.
 * @param update
 *   The value for the update field in the BAR config.
 *
 * @return
 *   - (0) if OK to reconfigure the device.
 *   - (-EIO) if I/O err and fail to reconfigure the device.
 */
int
nfp_ext_reconfig(struct nfp_hw *hw,
		uint32_t ctrl_ext,
		uint32_t update)
{
	int ret;

	rte_spinlock_lock(&hw->reconfig_lock);

	nn_cfg_writel(hw, NFP_NET_CFG_CTRL_WORD1, ctrl_ext);
	nn_cfg_writel(hw, NFP_NET_CFG_UPDATE, update);

	rte_wmb();

	ret = nfp_reconfig_real(hw, update);

	rte_spinlock_unlock(&hw->reconfig_lock);

	if (ret != 0) {
		PMD_DRV_LOG(ERR, "Error NFP ext reconfig: ctrl_ext=%#08x update=%#08x.",
				ctrl_ext, update);
		return -EIO;
	}

	return 0;
}

void
nfp_read_mac(struct nfp_hw *hw)
{
	uint32_t tmp;

	tmp = rte_be_to_cpu_32(nn_cfg_readl(hw, NFP_NET_CFG_MACADDR));
	memcpy(&hw->mac_addr.addr_bytes[0], &tmp, 4);

	tmp = rte_be_to_cpu_32(nn_cfg_readl(hw, NFP_NET_CFG_MACADDR + 4));
	memcpy(&hw->mac_addr.addr_bytes[4], &tmp, 2);
}

void
nfp_write_mac(struct nfp_hw *hw,
		uint8_t *mac)
{
	uint32_t mac0;
	uint16_t mac1;

	mac0 = *(uint32_t *)mac;
	nn_writel(rte_cpu_to_be_32(mac0), hw->ctrl_bar + NFP_NET_CFG_MACADDR);

	mac += 4;
	mac1 = *(uint16_t *)mac;
	nn_writew(rte_cpu_to_be_16(mac1),
			hw->ctrl_bar + NFP_NET_CFG_MACADDR + 6);
}

void
nfp_enable_queues(struct nfp_hw *hw,
		uint16_t nb_rx_queues,
		uint16_t nb_tx_queues)
{
	int i;
	uint64_t enabled_queues;

	/* Enabling the required TX queues in the device */
	enabled_queues = 0;
	for (i = 0; i < nb_tx_queues; i++)
		enabled_queues |= (1ULL << i);

	nn_cfg_writeq(hw, NFP_NET_CFG_TXRS_ENABLE, enabled_queues);

	/* Enabling the required RX queues in the device */
	enabled_queues = 0;
	for (i = 0; i < nb_rx_queues; i++)
		enabled_queues |= (1ULL << i);

	nn_cfg_writeq(hw, NFP_NET_CFG_RXRS_ENABLE, enabled_queues);
}

void
nfp_disable_queues(struct nfp_hw *hw)
{
	int ret;
	uint32_t update;
	uint32_t new_ctrl;

	nn_cfg_writeq(hw, NFP_NET_CFG_TXRS_ENABLE, 0);
	nn_cfg_writeq(hw, NFP_NET_CFG_RXRS_ENABLE, 0);

	new_ctrl = hw->ctrl & ~NFP_NET_CFG_CTRL_ENABLE;
	update = NFP_NET_CFG_UPDATE_GEN |
			NFP_NET_CFG_UPDATE_RING |
			NFP_NET_CFG_UPDATE_MSIX;

	if ((hw->cap & NFP_NET_CFG_CTRL_RINGCFG) != 0)
		new_ctrl &= ~NFP_NET_CFG_CTRL_RINGCFG;

	/* If an error when reconfig we avoid to change hw state */
	ret = nfp_reconfig(hw, new_ctrl, update);
	if (ret < 0)
		return;

	hw->ctrl = new_ctrl;
}
