// SPDX-License-Identifier: GPL-2.0
/* Marvell Octeon EP (EndPoint) VF Ethernet Driver
 *
 * Copyright (C) 2020 Marvell.
 *
 */
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/vmalloc.h>
#include "octep_vf_config.h"
#include "octep_vf_main.h"

/* When a new command is implemented, the below table should be updated
 * with new command and it's version info.
 */
static u32 pfvf_cmd_versions[OCTEP_PFVF_MBOX_CMD_MAX] = {
	[0 ... OCTEP_PFVF_MBOX_CMD_DEV_REMOVE] = OCTEP_PFVF_MBOX_VERSION_V1,
	[OCTEP_PFVF_MBOX_CMD_GET_FW_INFO ... OCTEP_PFVF_MBOX_NOTIF_LINK_STATUS] =
		OCTEP_PFVF_MBOX_VERSION_V2
};

int octep_vf_setup_mbox(struct octep_vf_device *oct)
{
	int ring = 0;

	oct->mbox = vzalloc(sizeof(*oct->mbox));
	if (!oct->mbox)
		return -1;

	mutex_init(&oct->mbox->lock);

	oct->hw_ops.setup_mbox_regs(oct, ring);
	INIT_WORK(&oct->mbox->wk.work, octep_vf_mbox_work);
	oct->mbox->wk.ctxptr = oct;
	oct->mbox_neg_ver = OCTEP_PFVF_MBOX_VERSION_CURRENT;
	dev_info(&oct->pdev->dev, "setup vf mbox successfully\n");
	return 0;
}

void octep_vf_delete_mbox(struct octep_vf_device *oct)
{
	if (oct->mbox) {
		if (work_pending(&oct->mbox->wk.work))
			cancel_work_sync(&oct->mbox->wk.work);

		mutex_destroy(&oct->mbox->lock);
		vfree(oct->mbox);
		oct->mbox = NULL;
		dev_info(&oct->pdev->dev, "Deleted vf mbox successfully\n");
	}
}

int octep_vf_mbox_version_check(struct octep_vf_device *oct)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_version.opcode = OCTEP_PFVF_MBOX_CMD_VERSION;
	cmd.s_version.version = OCTEP_PFVF_MBOX_VERSION_CURRENT;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret == OCTEP_PFVF_MBOX_CMD_STATUS_NACK) {
		dev_err(&oct->pdev->dev,
			"VF Mbox version is incompatible with PF\n");
		return -EINVAL;
	}
	oct->mbox_neg_ver = (u32)rsp.s_version.version;
	dev_dbg(&oct->pdev->dev,
		"VF Mbox version:%u Negotiated VF version with PF:%u\n",
		 (u32)cmd.s_version.version,
		 (u32)rsp.s_version.version);
	return 0;
}

void octep_vf_mbox_work(struct work_struct *work)
{
	struct octep_vf_mbox_wk *wk = container_of(work, struct octep_vf_mbox_wk, work);
	struct octep_vf_iface_link_info *link_info;
	struct octep_vf_device *oct = NULL;
	struct octep_vf_mbox *mbox = NULL;
	union octep_pfvf_mbox_word *notif;
	u64 pf_vf_data;

	oct = (struct octep_vf_device *)wk->ctxptr;
	link_info = &oct->link_info;
	mbox = oct->mbox;
	pf_vf_data = readq(mbox->mbox_read_reg);

	notif = (union octep_pfvf_mbox_word *)&pf_vf_data;

	switch (notif->s.opcode) {
	case OCTEP_PFVF_MBOX_NOTIF_LINK_STATUS:
		if (notif->s_link_status.status) {
			link_info->oper_up = OCTEP_PFVF_LINK_STATUS_UP;
			netif_carrier_on(oct->netdev);
			dev_info(&oct->pdev->dev, "netif_carrier_on\n");
		} else {
			link_info->oper_up = OCTEP_PFVF_LINK_STATUS_DOWN;
			netif_carrier_off(oct->netdev);
			dev_info(&oct->pdev->dev, "netif_carrier_off\n");
		}
		break;
	default:
		dev_err(&oct->pdev->dev,
			"Received unsupported notif %d\n", notif->s.opcode);
		break;
	}
}

static int __octep_vf_mbox_send_cmd(struct octep_vf_device *oct,
				    union octep_pfvf_mbox_word cmd,
				    union octep_pfvf_mbox_word *rsp)
{
	struct octep_vf_mbox *mbox = oct->mbox;
	u64 reg_val = 0ull;
	int count;

	if (!mbox)
		return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP;

	cmd.s.type = OCTEP_PFVF_MBOX_TYPE_CMD;
	writeq(cmd.u64, mbox->mbox_write_reg);

	/* No response for notification messages */
	if (!rsp)
		return 0;

	for (count = 0; count < OCTEP_PFVF_MBOX_TIMEOUT_WAIT_COUNT; count++) {
		usleep_range(1000, 1500);
		reg_val = readq(mbox->mbox_write_reg);
		if (reg_val != cmd.u64) {
			rsp->u64 = reg_val;
			break;
		}
	}
	if (count == OCTEP_PFVF_MBOX_TIMEOUT_WAIT_COUNT) {
		dev_err(&oct->pdev->dev, "mbox send command timed out\n");
		return OCTEP_PFVF_MBOX_CMD_STATUS_TIMEDOUT;
	}
	if (rsp->s.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "mbox_send: Received NACK\n");
		return OCTEP_PFVF_MBOX_CMD_STATUS_NACK;
	}
	rsp->u64 = reg_val;
	return 0;
}

int octep_vf_mbox_send_cmd(struct octep_vf_device *oct, union octep_pfvf_mbox_word cmd,
			   union octep_pfvf_mbox_word *rsp)
{
	struct octep_vf_mbox *mbox = oct->mbox;
	int ret;

	if (!mbox)
		return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP;
	mutex_lock(&mbox->lock);
	if (pfvf_cmd_versions[cmd.s.opcode] > oct->mbox_neg_ver) {
		dev_dbg(&oct->pdev->dev, "CMD:%d not supported in Version:%d\n",
			cmd.s.opcode, oct->mbox_neg_ver);
		mutex_unlock(&mbox->lock);
		return -EOPNOTSUPP;
	}
	ret = __octep_vf_mbox_send_cmd(oct, cmd, rsp);
	mutex_unlock(&mbox->lock);
	return ret;
}

int octep_vf_mbox_bulk_read(struct octep_vf_device *oct, enum octep_pfvf_mbox_opcode opcode,
			    u8 *data, int *size)
{
	struct octep_vf_mbox *mbox = oct->mbox;
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int data_len = 0, tmp_len = 0;
	int read_cnt, i = 0, ret;

	if (!mbox)
		return OCTEP_PFVF_MBOX_CMD_STATUS_NOT_SETUP;

	mutex_lock(&mbox->lock);
	cmd.u64 = 0;
	cmd.s_data.opcode = opcode;
	cmd.s_data.frag = 0;
	/* Send cmd to read data from PF */
	ret = __octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "send mbox cmd fail for data request\n");
		mutex_unlock(&mbox->lock);
		return ret;
	}
	/*  PF sends the data length of requested CMD
	 *  in  ACK
	 */
	data_len = *((int32_t *)rsp.s_data.data);
	tmp_len = data_len;
	cmd.u64 = 0;
	rsp.u64 = 0;
	cmd.s_data.opcode = opcode;
	cmd.s_data.frag = 1;
	while (data_len) {
		ret = __octep_vf_mbox_send_cmd(oct, cmd, &rsp);
		if (ret) {
			dev_err(&oct->pdev->dev, "send mbox cmd fail for data request\n");
			mutex_unlock(&mbox->lock);
			mbox->mbox_data.data_index = 0;
			memset(mbox->mbox_data.recv_data, 0, OCTEP_PFVF_MBOX_MAX_DATA_BUF_SIZE);
			return ret;
		}
		if (data_len > OCTEP_PFVF_MBOX_MAX_DATA_SIZE) {
			data_len -= OCTEP_PFVF_MBOX_MAX_DATA_SIZE;
			read_cnt = OCTEP_PFVF_MBOX_MAX_DATA_SIZE;
		} else {
			read_cnt = data_len;
			data_len = 0;
		}
		for (i = 0; i < read_cnt; i++) {
			mbox->mbox_data.recv_data[mbox->mbox_data.data_index] =
				rsp.s_data.data[i];
			mbox->mbox_data.data_index++;
		}
		cmd.u64 = 0;
		rsp.u64 = 0;
		cmd.s_data.opcode = opcode;
		cmd.s_data.frag = 1;
	}
	memcpy(data, mbox->mbox_data.recv_data, tmp_len);
	*size = tmp_len;
	mbox->mbox_data.data_index = 0;
	memset(mbox->mbox_data.recv_data, 0, OCTEP_PFVF_MBOX_MAX_DATA_BUF_SIZE);
	mutex_unlock(&mbox->lock);
	return 0;
}

int octep_vf_mbox_set_mtu(struct octep_vf_device *oct, int mtu)
{
	int frame_size = mtu + ETH_HLEN + ETH_FCS_LEN;
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret = 0;

	if (mtu < ETH_MIN_MTU || frame_size > ETH_MAX_MTU) {
		dev_err(&oct->pdev->dev,
			"Failed to set MTU to %d MIN MTU:%d MAX MTU:%d\n",
			mtu, ETH_MIN_MTU, ETH_MAX_MTU);
		return -EINVAL;
	}

	cmd.u64 = 0;
	cmd.s_set_mtu.opcode = OCTEP_PFVF_MBOX_CMD_SET_MTU;
	cmd.s_set_mtu.mtu = mtu;

	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Mbox send failed; err=%d\n", ret);
		return ret;
	}
	if (rsp.s_set_mtu.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Received Mbox NACK from PF for MTU:%d\n", mtu);
		return -EINVAL;
	}

	return 0;
}

int octep_vf_mbox_set_mac_addr(struct octep_vf_device *oct, char *mac_addr)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int i, ret;

	cmd.u64 = 0;
	cmd.s_set_mac.opcode = OCTEP_PFVF_MBOX_CMD_SET_MAC_ADDR;
	for (i = 0; i < ETH_ALEN; i++)
		cmd.s_set_mac.mac_addr[i] = mac_addr[i];
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Mbox send failed; err = %d\n", ret);
		return ret;
	}
	if (rsp.s_set_mac.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "received NACK\n");
		return -EINVAL;
	}
	return 0;
}

int octep_vf_mbox_get_mac_addr(struct octep_vf_device *oct, char *mac_addr)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int i, ret;

	cmd.u64 = 0;
	cmd.s_set_mac.opcode = OCTEP_PFVF_MBOX_CMD_GET_MAC_ADDR;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "get_mac: mbox send failed; err = %d\n", ret);
		return ret;
	}
	if (rsp.s_set_mac.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "get_mac: received NACK\n");
		return -EINVAL;
	}
	for (i = 0; i < ETH_ALEN; i++)
		mac_addr[i] = rsp.s_set_mac.mac_addr[i];
	return 0;
}

int octep_vf_mbox_set_rx_state(struct octep_vf_device *oct, bool state)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_link_state.opcode = OCTEP_PFVF_MBOX_CMD_SET_RX_STATE;
	cmd.s_link_state.state = state;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Set Rx state via VF Mbox send failed\n");
		return ret;
	}
	if (rsp.s_link_state.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Set Rx state received NACK\n");
		return -EINVAL;
	}
	return 0;
}

int octep_vf_mbox_set_link_status(struct octep_vf_device *oct, bool status)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_link_status.opcode = OCTEP_PFVF_MBOX_CMD_SET_LINK_STATUS;
	cmd.s_link_status.status = status;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Set link status via VF Mbox send failed\n");
		return ret;
	}
	if (rsp.s_link_status.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Set link status received NACK\n");
		return -EINVAL;
	}
	return 0;
}

int octep_vf_mbox_get_link_status(struct octep_vf_device *oct, u8 *oper_up)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_link_status.opcode = OCTEP_PFVF_MBOX_CMD_GET_LINK_STATUS;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Get link status via VF Mbox send failed\n");
		return ret;
	}
	if (rsp.s_link_status.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Get link status received NACK\n");
		return -EINVAL;
	}
	*oper_up = rsp.s_link_status.status;
	return 0;
}

int octep_vf_mbox_dev_remove(struct octep_vf_device *oct)
{
	union octep_pfvf_mbox_word cmd;
	int ret;

	cmd.u64 = 0;
	cmd.s.opcode = OCTEP_PFVF_MBOX_CMD_DEV_REMOVE;
	ret = octep_vf_mbox_send_cmd(oct, cmd, NULL);
	return ret;
}

int octep_vf_mbox_get_fw_info(struct octep_vf_device *oct)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_fw_info.opcode = OCTEP_PFVF_MBOX_CMD_GET_FW_INFO;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Get link status via VF Mbox send failed\n");
		return ret;
	}
	if (rsp.s_fw_info.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Get link status received NACK\n");
		return -EINVAL;
	}
	oct->fw_info.pkind = rsp.s_fw_info.pkind;
	oct->fw_info.fsz = rsp.s_fw_info.fsz;
	oct->fw_info.rx_ol_flags = rsp.s_fw_info.rx_ol_flags;
	oct->fw_info.tx_ol_flags = rsp.s_fw_info.tx_ol_flags;

	return 0;
}

int octep_vf_mbox_set_offloads(struct octep_vf_device *oct, u16 tx_offloads,
			       u16 rx_offloads)
{
	union octep_pfvf_mbox_word cmd;
	union octep_pfvf_mbox_word rsp;
	int ret;

	cmd.u64 = 0;
	cmd.s_offloads.opcode = OCTEP_PFVF_MBOX_CMD_SET_OFFLOADS;
	cmd.s_offloads.rx_ol_flags = rx_offloads;
	cmd.s_offloads.tx_ol_flags = tx_offloads;
	ret = octep_vf_mbox_send_cmd(oct, cmd, &rsp);
	if (ret) {
		dev_err(&oct->pdev->dev, "Set offloads via VF Mbox send failed\n");
		return ret;
	}
	if (rsp.s_link_state.type != OCTEP_PFVF_MBOX_TYPE_RSP_ACK) {
		dev_err(&oct->pdev->dev, "Set offloads received NACK\n");
		return -EINVAL;
	}
	return 0;
}
