// SPDX-License-Identifier: GPL-2.0
/* Marvell OcteonTx2 RVU Admin Function driver
 *
 * Copyright (C) 2018 Marvell International Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/pci.h>

#include "rvu_reg.h"
#include "mbox.h"
#include "rvu_trace.h"

static const u16 msgs_offset = ALIGN(sizeof(struct mbox_hdr), MBOX_MSG_ALIGN);

void __otx2_mbox_reset(struct otx2_mbox *mbox, int devid)
{
	void *hw_mbase = mbox->hwbase + (devid * MBOX_SIZE);
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	struct mbox_hdr *tx_hdr, *rx_hdr;

	tx_hdr = hw_mbase + mbox->tx_start;
	rx_hdr = hw_mbase + mbox->rx_start;

	mdev->msg_size = 0;
	mdev->rsp_size = 0;
	tx_hdr->num_msgs = 0;
	tx_hdr->msg_size = 0;
	rx_hdr->num_msgs = 0;
	rx_hdr->msg_size = 0;
}
EXPORT_SYMBOL(__otx2_mbox_reset);

void otx2_mbox_reset(struct otx2_mbox *mbox, int devid)
{
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];

	spin_lock(&mdev->mbox_lock);
	__otx2_mbox_reset(mbox, devid);
	spin_unlock(&mdev->mbox_lock);
}
EXPORT_SYMBOL(otx2_mbox_reset);

void otx2_mbox_destroy(struct otx2_mbox *mbox)
{
	mbox->reg_base = NULL;
	mbox->hwbase = NULL;

	kfree(mbox->dev);
	mbox->dev = NULL;
}
EXPORT_SYMBOL(otx2_mbox_destroy);

int otx2_mbox_init(struct otx2_mbox *mbox, void *hwbase, struct pci_dev *pdev,
		   void *reg_base, int direction, int ndevs)
{
	struct otx2_mbox_dev *mdev;
	int devid;

	switch (direction) {
	case MBOX_DIR_AFPF:
	case MBOX_DIR_PFVF:
		mbox->tx_start = MBOX_DOWN_TX_START;
		mbox->rx_start = MBOX_DOWN_RX_START;
		mbox->tx_size  = MBOX_DOWN_TX_SIZE;
		mbox->rx_size  = MBOX_DOWN_RX_SIZE;
		break;
	case MBOX_DIR_PFAF:
	case MBOX_DIR_VFPF:
		mbox->tx_start = MBOX_DOWN_RX_START;
		mbox->rx_start = MBOX_DOWN_TX_START;
		mbox->tx_size  = MBOX_DOWN_RX_SIZE;
		mbox->rx_size  = MBOX_DOWN_TX_SIZE;
		break;
	case MBOX_DIR_AFPF_UP:
	case MBOX_DIR_PFVF_UP:
		mbox->tx_start = MBOX_UP_TX_START;
		mbox->rx_start = MBOX_UP_RX_START;
		mbox->tx_size  = MBOX_UP_TX_SIZE;
		mbox->rx_size  = MBOX_UP_RX_SIZE;
		break;
	case MBOX_DIR_PFAF_UP:
	case MBOX_DIR_VFPF_UP:
		mbox->tx_start = MBOX_UP_RX_START;
		mbox->rx_start = MBOX_UP_TX_START;
		mbox->tx_size  = MBOX_UP_RX_SIZE;
		mbox->rx_size  = MBOX_UP_TX_SIZE;
		break;
	default:
		return -ENODEV;
	}

	switch (direction) {
	case MBOX_DIR_AFPF:
	case MBOX_DIR_AFPF_UP:
		mbox->trigger = RVU_AF_AFPF_MBOX0;
		mbox->tr_shift = 4;
		break;
	case MBOX_DIR_PFAF:
	case MBOX_DIR_PFAF_UP:
		mbox->trigger = RVU_PF_PFAF_MBOX1;
		mbox->tr_shift = 0;
		break;
	case MBOX_DIR_PFVF:
	case MBOX_DIR_PFVF_UP:
		mbox->trigger = RVU_PF_VFX_PFVF_MBOX0;
		mbox->tr_shift = 12;
		break;
	case MBOX_DIR_VFPF:
	case MBOX_DIR_VFPF_UP:
		mbox->trigger = RVU_VF_VFPF_MBOX1;
		mbox->tr_shift = 0;
		break;
	default:
		return -ENODEV;
	}

	mbox->reg_base = reg_base;
	mbox->hwbase = hwbase;
	mbox->pdev = pdev;

	mbox->dev = kcalloc(ndevs, sizeof(struct otx2_mbox_dev), GFP_KERNEL);
	if (!mbox->dev) {
		otx2_mbox_destroy(mbox);
		return -ENOMEM;
	}

	mbox->ndevs = ndevs;
	for (devid = 0; devid < ndevs; devid++) {
		mdev = &mbox->dev[devid];
		mdev->mbase = mbox->hwbase + (devid * MBOX_SIZE);
		spin_lock_init(&mdev->mbox_lock);
		/* Init header to reset value */
		otx2_mbox_reset(mbox, devid);
	}

	return 0;
}
EXPORT_SYMBOL(otx2_mbox_init);

int otx2_mbox_wait_for_rsp(struct otx2_mbox *mbox, int devid)
{
	unsigned long timeout = jiffies + msecs_to_jiffies(MBOX_RSP_TIMEOUT);
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	struct device *sender = &mbox->pdev->dev;

	while (!time_after(jiffies, timeout)) {
		if (mdev->num_msgs == mdev->msgs_acked)
			return 0;
		usleep_range(800, 1000);
	}
	dev_dbg(sender, "timed out while waiting for rsp\n");
	return -EIO;
}
EXPORT_SYMBOL(otx2_mbox_wait_for_rsp);

int otx2_mbox_busy_poll_for_rsp(struct otx2_mbox *mbox, int devid)
{
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	unsigned long timeout = jiffies + 1 * HZ;

	while (!time_after(jiffies, timeout)) {
		if (mdev->num_msgs == mdev->msgs_acked)
			return 0;
		cpu_relax();
	}
	return -EIO;
}
EXPORT_SYMBOL(otx2_mbox_busy_poll_for_rsp);

void otx2_mbox_msg_send(struct otx2_mbox *mbox, int devid)
{
	void *hw_mbase = mbox->hwbase + (devid * MBOX_SIZE);
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	struct mbox_hdr *tx_hdr, *rx_hdr;

	tx_hdr = hw_mbase + mbox->tx_start;
	rx_hdr = hw_mbase + mbox->rx_start;

	/* If bounce buffer is implemented copy mbox messages from
	 * bounce buffer to hw mbox memory.
	 */
	if (mdev->mbase != hw_mbase)
		memcpy(hw_mbase + mbox->tx_start + msgs_offset,
		       mdev->mbase + mbox->tx_start + msgs_offset,
		       mdev->msg_size);

	spin_lock(&mdev->mbox_lock);

	tx_hdr->msg_size = mdev->msg_size;

	/* Reset header for next messages */
	mdev->msg_size = 0;
	mdev->rsp_size = 0;
	mdev->msgs_acked = 0;

	/* Sync mbox data into memory */
	smp_wmb();

	/* num_msgs != 0 signals to the peer that the buffer has a number of
	 * messages.  So this should be written after writing all the messages
	 * to the shared memory.
	 */
	tx_hdr->num_msgs = mdev->num_msgs;
	rx_hdr->num_msgs = 0;

	trace_otx2_msg_send(mbox->pdev, tx_hdr->num_msgs, tx_hdr->msg_size);

	spin_unlock(&mdev->mbox_lock);

	/* The interrupt should be fired after num_msgs is written
	 * to the shared memory
	 */
	writeq(1, (void __iomem *)mbox->reg_base +
	       (mbox->trigger | (devid << mbox->tr_shift)));
}
EXPORT_SYMBOL(otx2_mbox_msg_send);

struct mbox_msghdr *otx2_mbox_alloc_msg_rsp(struct otx2_mbox *mbox, int devid,
					    int size, int size_rsp)
{
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	struct mbox_msghdr *msghdr = NULL;

	spin_lock(&mdev->mbox_lock);
	size = ALIGN(size, MBOX_MSG_ALIGN);
	size_rsp = ALIGN(size_rsp, MBOX_MSG_ALIGN);
	/* Check if there is space in mailbox */
	if ((mdev->msg_size + size) > mbox->tx_size - msgs_offset)
		goto exit;
	if ((mdev->rsp_size + size_rsp) > mbox->rx_size - msgs_offset)
		goto exit;

	if (mdev->msg_size == 0)
		mdev->num_msgs = 0;
	mdev->num_msgs++;

	msghdr = mdev->mbase + mbox->tx_start + msgs_offset + mdev->msg_size;

	/* Clear the whole msg region */
	memset(msghdr, 0, size);
	/* Init message header with reset values */
	msghdr->ver = OTX2_MBOX_VERSION;
	mdev->msg_size += size;
	mdev->rsp_size += size_rsp;
	msghdr->next_msgoff = mdev->msg_size + msgs_offset;
exit:
	spin_unlock(&mdev->mbox_lock);

	return msghdr;
}
EXPORT_SYMBOL(otx2_mbox_alloc_msg_rsp);

struct mbox_msghdr *otx2_mbox_get_rsp(struct otx2_mbox *mbox, int devid,
				      struct mbox_msghdr *msg)
{
	unsigned long imsg = mbox->tx_start + msgs_offset;
	unsigned long irsp = mbox->rx_start + msgs_offset;
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	u16 msgs;

	spin_lock(&mdev->mbox_lock);

	if (mdev->num_msgs != mdev->msgs_acked)
		goto error;

	for (msgs = 0; msgs < mdev->msgs_acked; msgs++) {
		struct mbox_msghdr *pmsg = mdev->mbase + imsg;
		struct mbox_msghdr *prsp = mdev->mbase + irsp;

		if (msg == pmsg) {
			if (pmsg->id != prsp->id)
				goto error;
			spin_unlock(&mdev->mbox_lock);
			return prsp;
		}

		imsg = mbox->tx_start + pmsg->next_msgoff;
		irsp = mbox->rx_start + prsp->next_msgoff;
	}

error:
	spin_unlock(&mdev->mbox_lock);
	return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL(otx2_mbox_get_rsp);

int otx2_mbox_check_rsp_msgs(struct otx2_mbox *mbox, int devid)
{
	unsigned long ireq = mbox->tx_start + msgs_offset;
	unsigned long irsp = mbox->rx_start + msgs_offset;
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	int rc = -ENODEV;
	u16 msgs;

	spin_lock(&mdev->mbox_lock);

	if (mdev->num_msgs != mdev->msgs_acked)
		goto exit;

	for (msgs = 0; msgs < mdev->msgs_acked; msgs++) {
		struct mbox_msghdr *preq = mdev->mbase + ireq;
		struct mbox_msghdr *prsp = mdev->mbase + irsp;

		if (preq->id != prsp->id) {
			trace_otx2_msg_check(mbox->pdev, preq->id,
					     prsp->id, prsp->rc);
			goto exit;
		}
		if (prsp->rc) {
			rc = prsp->rc;
			trace_otx2_msg_check(mbox->pdev, preq->id,
					     prsp->id, prsp->rc);
			goto exit;
		}

		ireq = mbox->tx_start + preq->next_msgoff;
		irsp = mbox->rx_start + prsp->next_msgoff;
	}
	rc = 0;
exit:
	spin_unlock(&mdev->mbox_lock);
	return rc;
}
EXPORT_SYMBOL(otx2_mbox_check_rsp_msgs);

int
otx2_reply_invalid_msg(struct otx2_mbox *mbox, int devid, u16 pcifunc, u16 id)
{
	struct msg_rsp *rsp;

	rsp = (struct msg_rsp *)
	       otx2_mbox_alloc_msg(mbox, devid, sizeof(*rsp));
	if (!rsp)
		return -ENOMEM;
	rsp->hdr.id = id;
	rsp->hdr.sig = OTX2_MBOX_RSP_SIG;
	rsp->hdr.rc = MBOX_MSG_INVALID;
	rsp->hdr.pcifunc = pcifunc;
	return 0;
}
EXPORT_SYMBOL(otx2_reply_invalid_msg);

bool otx2_mbox_nonempty(struct otx2_mbox *mbox, int devid)
{
	struct otx2_mbox_dev *mdev = &mbox->dev[devid];
	bool ret;

	spin_lock(&mdev->mbox_lock);
	ret = mdev->num_msgs != 0;
	spin_unlock(&mdev->mbox_lock);

	return ret;
}
EXPORT_SYMBOL(otx2_mbox_nonempty);

const char *otx2_mbox_id2name(u16 id)
{
	switch (id) {
#define M(_name, _id, _1, _2, _3) case _id: return # _name;
	MBOX_MESSAGES
#undef M
	default:
		return "INVALID ID";
	}
}
EXPORT_SYMBOL(otx2_mbox_id2name);

MODULE_AUTHOR("Marvell International Ltd.");
MODULE_LICENSE("GPL v2");
