/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#ifndef _AML_MBOX_H_
#define _AML_MBOX_H_
#include <linux/cdev.h>
#include <linux/completion.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox_controller.h>
#include "aml_mbox_cmd.h"

#define MBOX_DATA_SIZE          0x60
#define MBOX_NO_FEEDBACK	0xA5A5A5A5

enum aml_mbox_sync {
	MBOX_ASYNC = 0,    /* Only send data, not get data from remote */
	MBOX_SYNC  = 1,    /* Send data, also can get data from remote if needed */
	MBOX_TSYNC = 2,    /* Send data, wait for data feedback, the wake up wait task */
};

/**
 * struct aml_mbox_data - mbox physical channel info
 * @cmd:               Use to remote mailbox driver recognize callback function
 * @txbuf:             Mbox tansfer data buffer
 * @rxbuf:             Mbox receive data buffer
 * @txsize:            Mbox transfer data size
 * @rxsize:            Mbox receive data size
 * @sync:              Transfer data to sync or not
 * @rev_complete:      Feedback complete address
 * @complete:          Use to process block, until resource release
 */
struct aml_mbox_data {
	u32 cmd;
	void *txbuf;
	void *rxbuf;
	u32 txsize;
	u32 rxsize;
	u8 sync;
	u64 rev_complete;
	struct completion complete;
};

/**
 * struct aml_mbox_chan - mbox physical channel info
 * @mboxid:               Mbox physical channel id
 * @irq_nums:             The total mbox irq nums
 * @mbox_irqctlr_addr:    Mbox irq controller address
 * @mbox_irqclr_addr:     Mbox irq clear address
 * @mbox_irq:             Mbox channel irq number
 * @tx_chan:              If transfer channel
 * @data:                 For payload mbox driver oldest version
 * @mbox_wr_addr:         Mbox data write address
 * @mbox_rd_addr:         Mbox data read address
 * @mbox_fset_addr:       Mbox set address
 * @mbox_fclr_addr:       Mbox clear address
 * @mbox_fsts_addr:       Mbox status address
 * @mutex:                Mbox channel mutex lock
 * @mbox:                 Mbox controller
 * @tx_complete:          Use to recognize the which channel send data
 */
struct aml_mbox_chan {
	/* mbox fifo */
	u32 mboxid;
	u32 irq_nums;
	void __iomem *mbox_irqctlr_addr;
	void __iomem *mbox_irqclr_addr;
	void __iomem *mbox_irqsts_addr;

	/* mbox payload */
	u32 mbox_irq;
	bool tx_chan;
	void *data;

	/* mbox common struct */
	void __iomem *mbox_wr_addr;
	void __iomem *mbox_rd_addr;
	void __iomem *mbox_fset_addr;
	void __iomem *mbox_fclr_addr;
	void __iomem *mbox_fsts_addr;
	struct mutex mutex; /* for aml mbox chan mutex */
	struct mbox_controller *mbox;
	void *tx_complete;
};

static inline void mbox_receive_callback(struct mbox_client *cl, void *mssg)
{
	dev_err(cl->dev, "Driver need add callback function\n");
}

/**
 * aml_mbox_request_channel_byidx - Request mbox channel by index
 * @dev: client device node
 * @name: mbox chan index
 * Return: The mbox channel if success
 */
static inline struct mbox_chan *aml_mbox_request_channel_byidx(struct device *dev, int idx)
{
	struct mbox_client *mbox_client;
	struct mbox_chan *mbox_chan;

	mbox_client = devm_kzalloc(dev, sizeof(*mbox_client), GFP_KERNEL);
	if (IS_ERR_OR_NULL(mbox_client))
		return ERR_PTR(-ENOMEM);
	mbox_client->dev = dev;
	mbox_client->tx_block = true;
	mbox_client->tx_tout = 10000;
	mbox_client->rx_callback = mbox_receive_callback;
	mbox_chan = mbox_request_channel(mbox_client, idx);
	if (IS_ERR_OR_NULL(mbox_chan)) {
		devm_kfree(dev, mbox_client);
		dev_err(dev, "Not find mbox channel byidx\n");
	}
	return mbox_chan;
}

/**
 * aml_mbox_request_channel_byname - Request mbox by name
 * @dev: client device node
 * @name: mbox chan name
 * Return: The mbox channel if success
 */
static inline struct mbox_chan *aml_mbox_request_channel_byname(struct device *dev, char *name)
{
	struct mbox_client *mbox_client;
	struct mbox_chan *mbox_chan;

	mbox_client = devm_kzalloc(dev, sizeof(*mbox_client), GFP_KERNEL);
	if (IS_ERR_OR_NULL(mbox_client))
		return ERR_PTR(-ENOMEM);
	mbox_client->dev = dev;
	mbox_client->tx_block = true;
	mbox_client->tx_tout = 10000;
	mbox_client->rx_callback = mbox_receive_callback;
	mbox_chan = mbox_request_channel_byname(mbox_client, name);
	if (IS_ERR_OR_NULL(mbox_chan)) {
		devm_kfree(dev, mbox_client);
		dev_err(dev, "Not find mbox channel byname\n");
	}
	return mbox_chan;
}

/**
 * aml_mbox_transfer_data - A way for transfer mbox data
 * @mbox_chan:            Mbox channel, this requested by mbox consumer driver
 * @cmd:                  Mbox cmd, use remote driver recognize callback function
 * @txbuf:                Mbox transfer data buffer
 * @txsize:               Mbox transfer data size
 * @rxbuf:                Mbox receive data size
 * @rxsize:               Mbox receive data size
 * @sync                  Mbox Sync info
 */
static inline int aml_mbox_transfer_data(struct mbox_chan *mbox_chan, int cmd,
					 void *txbuf, u32 txsize, void *rxbuf, u32 rxsize,
					 enum aml_mbox_sync sync)
{
	struct aml_mbox_data aml_data;
	struct aml_mbox_chan *aml_chan;
	int ret;

	if (IS_ERR_OR_NULL(mbox_chan)) {
		pr_err("mbox chan is NULL\n");
		return -EINVAL;
	}
	aml_chan = mbox_chan->con_priv;
	init_completion(&aml_data.complete);
	aml_data.sync = sync;
	aml_data.cmd = cmd;
	aml_data.txbuf = txbuf;
	aml_data.txsize = txsize;
	aml_data.rxbuf = rxbuf;
	aml_data.rxsize = rxsize;
	mutex_lock(&aml_chan->mutex);
	ret = mbox_send_message(mbox_chan, &aml_data);
	mutex_unlock(&aml_chan->mutex);
	if (ret < 0) {
		dev_err(mbox_chan->cl->dev, "Fail to send mbox data %d\n", ret);
		return ret;
	}
	if (sync == MBOX_TSYNC)
		ret = wait_for_completion_killable(&aml_data.complete);
	return ret;
}

#endif
