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

#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/mailbox_client.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/of_device.h>
#include <linux/amlogic/aml_mbox.h>
#include <dt-bindings/mailbox/t3-mbox.h>
#include <dt-bindings/mailbox/t7-mbox.h>
#include <dt-bindings/mailbox/sc2-mbox.h>
#include <dt-bindings/mailbox/s4-mbox.h>
#include <dt-bindings/mailbox/t5m-mbox.h>
#include <dt-bindings/mailbox/t3x-mbox.h>
#include <dt-bindings/mailbox/s5-mbox.h>
#include <dt-bindings/mailbox/s1a-mbox.h>
#include "meson_mbox_fifo.h"
#include "meson_mbox_comm.h"

#define ASYNC	1
#define SYNC	2
#define MBOX_HEAD_SIZE	0x1C
#define DRIVER_NAME	"meson_mbox_fifo"

struct mbox_header {
	u32 status;
	u64 task;
	u64 complete;
	u64 ullclt;
} __packed;

struct mbox_data {
	struct mbox_header mbox_header;
	char data[MBOX_DATA_SIZE];
} __packed;

struct mbox_domain {
	u32 drvid;
	u32 mboxid;
	u32 flags;
};

struct mbox_domain_data {
	struct mbox_domain *mbox_domains;
	u32 domain_counts;
};

struct aml_chan_priv {
	struct aml_mbox_chan *aml_chan;
	u32 mbox_nums;
};

#define MBOX_DOMAIN(drv_id, mbox_id, flag)		\
{					\
		.flags = flag,					\
		.drvid = drv_id,				\
		.mboxid = mbox_id,				\
}

static void mbox_fifo_write(void __iomem *to, void *from, long count)
{
	int i = 0;
	long len = count / 4;
	long rlen = count % 4;
	u32 rdata = 0;
	u32 *p = (u32 *)from;

	while (len > 0) {
		writel(*(const u32 *)p, (to + 4 * i));
		len--;
		i++;
		p++;
	}

	if (rlen != 0) {
		memcpy(&rdata, p, rlen);
		writel(rdata, (to + 4 * i));
	}
}

static void mbox_fifo_read(void *to, void __iomem *from, long count)
{
	int i = 0;
	long len = count / 4;
	long rlen = count % 4;
	u32 rdata = 0;
	u32 *p = to;

	while (len > 0) {
		*p = readl(from + (4 * i));
		len--;
		i++;
		p++;
	}

	if (rlen != 0) {
		rdata = readl(from + (4 * i));
		memcpy(p, &rdata, rlen);
	}
}

static void mbox_fifo_clr(void __iomem *to, long count)
{
	int i = 0;
	int len = (count + 3) / 4;

	while (len > 0) {
		writel(0, (to + 4 * i));
		len--;
		i++;
	}
}

void mbox_irq_clean(struct aml_mbox_chan *aml_chan, bool ack)
{
	u64 h_sts, l_sts;
	u64 mask;

	if (ack)
		mask = IRQ_SENDACK_BIT(aml_chan->mboxid);
	else
		mask = IRQ_REV_BIT(aml_chan->mboxid);
	if (aml_chan->irq_nums / MHUIRQ_MAXNUM_DEF == 2) {
		h_sts = (mask >> MBOX_IRQSHIFT) & MBOX_IRQMASK;
		l_sts = mask & MBOX_IRQMASK;
		writel(l_sts, aml_chan->mbox_irqclr_addr);
		writel(h_sts, aml_chan->mbox_irqclr_addr + 4);
	} else {
		writel((mask & MBOX_IRQMASK), aml_chan->mbox_irqclr_addr);
	}
}

static void mbox_wakeup_wait_task(void *mssg)
{
	struct aml_mbox_data *aml_rdata = mssg;
	struct completion *p_comp = (struct completion *)(unsigned long)aml_rdata->rev_complete;
	struct aml_mbox_data *aml_sdata = container_of(p_comp, struct aml_mbox_data, complete);

	if (IS_ERR_OR_NULL(p_comp))
		return;

	if (aml_rdata->rxsize && aml_rdata->rxbuf) {
		aml_sdata->rxsize = aml_rdata->rxsize;
		memcpy(aml_sdata->rxbuf, aml_rdata->rxbuf, aml_rdata->rxsize);
	}
	complete(p_comp);
}

static void mbox_isr_handler(struct aml_mbox_chan *aml_chan)
{
	union mbox_stat mbox_stat;
	struct mbox_cmd_t *mbox_cmd = &mbox_stat.mbox_cmd_t;
	struct aml_mbox_data aml_data;
	struct mbox_header *mbox_header;
	struct mbox_data mbox_data;
	struct mbox_chan *mbox_chan;
	struct mbox_controller *mbox_contr = aml_chan->mbox;

	mbox_stat.set_cmd = readl(aml_chan->mbox_fsts_addr);
	if (mbox_stat.set_cmd) {
		if (mbox_cmd->size >= MBOX_HEAD_SIZE) {
			aml_data.cmd = mbox_cmd->cmd;
			if (mbox_cmd->size > (MBOX_DATA_SIZE + MBOX_HEAD_SIZE)) {
				pr_err("%s: rxsize %x > %x\n",
					__func__, mbox_cmd->size,
					MBOX_DATA_SIZE + MBOX_HEAD_SIZE);
				mbox_cmd->size = MBOX_DATA_SIZE + MBOX_HEAD_SIZE;
			}
			mbox_fifo_read(&mbox_data, aml_chan->mbox_rd_addr, mbox_cmd->size);
			mbox_header = &mbox_data.mbox_header;
			if (mbox_header->complete == MBOX_NO_FEEDBACK)
				goto mbox_isr_handler_done;

			aml_data.rxsize = mbox_cmd->size - MBOX_HEAD_SIZE;
			aml_data.rxbuf = mbox_data.data;
			if (mbox_header->complete != 0) {
				aml_data.rev_complete = mbox_header->complete;
				mbox_wakeup_wait_task(&aml_data);
				goto mbox_isr_handler_done;
			}

			/*transfer data to client data
			 * status contains the mbox client id
			 */
			if (mbox_header->status < mbox_contr->num_chans) {
				mbox_chan = &mbox_contr->chans[mbox_header->status];
				mbox_chan_received_data(mbox_chan, &aml_data);
			}
		}
	}
mbox_isr_handler_done:
	mbox_irq_clean(aml_chan, 0);
	writel(~0, aml_chan->mbox_fclr_addr);
}

void mbox_ack_isr_handler(struct mbox_chan *mbox_chan)
{
	struct aml_mbox_chan *aml_chan = mbox_chan->con_priv;
	struct aml_mbox_data *aml_data = mbox_chan->active_req;

	if (aml_data && aml_data->sync == MBOX_SYNC) {
		if (aml_data->rxbuf && aml_data->rxsize) {
			if (aml_data->rxsize > MBOX_DATA_SIZE) {
				dev_err(mbox_chan->cl->dev, "%s: rxsize %x > %x\n",
					__func__, aml_data->txsize, MBOX_DATA_SIZE);
				aml_data->rxsize = MBOX_DATA_SIZE;
			}
			mbox_fifo_read(aml_data->rxbuf,
				       aml_chan->mbox_rd_addr + MBOX_HEAD_SIZE,
				       aml_data->rxsize);
		}
	}
	aml_chan->tx_complete = NULL;
	mbox_fifo_clr(aml_chan->mbox_wr_addr, MBOX_FIFO_SIZE);
	mbox_irq_clean(aml_chan, 1);
	mbox_chan_txdone(mbox_chan, 0);
}

static u64 mbox_irqstatus(struct aml_mbox_chan *aml_chan)
{
	u64 sts, h_sts, l_sts;

	if (aml_chan->irq_nums / MHUIRQ_MAXNUM_DEF == 2) {
		l_sts = readl(aml_chan->mbox_irqsts_addr);
		h_sts = readl(aml_chan->mbox_irqsts_addr + 4);
		sts = (h_sts << MBOX_IRQSHIFT) | (l_sts & MBOX_IRQMASK);
	} else {
		sts = readl(aml_chan->mbox_irqsts_addr);
	}
	pr_debug("%s irq %llx\n", __func__, sts);
	return sts;
}

static irqreturn_t mbox_handler(int irq, void *p)
{
	struct mbox_controller *mbox_cons = p;
	struct mbox_chan *mbox_chan = &mbox_cons->chans[0];
	struct aml_mbox_chan *aml_chan = mbox_chan->con_priv;
	u64 irq_sts = mbox_irqstatus(aml_chan);
	int out_cnt = mbox_cons->num_chans;
	u64 irq_prests;
	int idx;

	while (irq_sts && (out_cnt != 0)) {
		irq_prests = irq_sts;
		for (idx = 0; idx < mbox_cons->num_chans; idx++) {
			mbox_chan = &mbox_cons->chans[idx];
			aml_chan = mbox_chan->con_priv;
			if (irq_sts & (BIT_ULL(aml_chan->mboxid * 2 + 1))) {
				irq_sts ^= BIT_ULL(aml_chan->mboxid * 2 + 1);
				mbox_chan = container_of(aml_chan->tx_complete,
							 struct mbox_chan, tx_complete);
				if (IS_ERR(mbox_chan)) {
					mbox_irq_clean(aml_chan, 1);
					continue;
				}
				mbox_ack_isr_handler(mbox_chan);
			} else if (irq_sts & (BIT_ULL(aml_chan->mboxid * 2))) {
				irq_sts ^= BIT_ULL(aml_chan->mboxid * 2);
				mbox_isr_handler(aml_chan);
			}
		}
		irq_sts = mbox_irqstatus(aml_chan);
		irq_sts = (irq_sts | irq_prests) ^ irq_prests;
		out_cnt--;
	}

	pr_debug("%s irq\n", __func__);
	return IRQ_HANDLED;
}

static int mbox_transfer_data(struct mbox_chan *mbox_chan, void *data)
{
	struct aml_mbox_chan *aml_chan = mbox_chan->con_priv;
	struct aml_mbox_data *aml_data = data;
	union mbox_stat mbox_stat;
	struct mbox_cmd_t *mbox_cmd = &mbox_stat.mbox_cmd_t;
	struct mbox_header mbox_header;

	mbox_cmd->cmd = aml_data->cmd;
	if (aml_data->txsize > MBOX_DATA_SIZE) {
		dev_err(mbox_chan->cl->dev, "%s: size %x > %x\n",
			__func__, aml_data->txsize, MBOX_DATA_SIZE);
		return -EINVAL;
	}
	mbox_cmd->size = aml_data->txsize + MBOX_HEAD_SIZE;
	if (aml_data->sync == MBOX_SYNC)
		mbox_cmd->sync = SYNC;
	else
		mbox_cmd->sync = ASYNC;
	aml_chan->tx_complete = &mbox_chan->tx_complete;
	memset(&mbox_header, 0, sizeof(mbox_header));
	if (aml_data->sync == MBOX_TSYNC)
		mbox_header.complete = (unsigned long)(&aml_data->complete);
	else
		mbox_header.complete = MBOX_NO_FEEDBACK;
	mbox_fifo_write(aml_chan->mbox_wr_addr, &mbox_header, MBOX_HEAD_SIZE);

	if (aml_data->txbuf) {
		mbox_fifo_write(aml_chan->mbox_wr_addr + MBOX_HEAD_SIZE,
				aml_data->txbuf, aml_data->txsize);
	}
	writel(mbox_stat.set_cmd, aml_chan->mbox_fset_addr);
	return 0;
}

static int mbox_startup(struct mbox_chan *mbox_chan)
{
	return 0;
}

static void mbox_shutdown(struct mbox_chan *mbox_chan)
{
}

static bool mbox_last_tx_done(struct mbox_chan *mbox_chan)
{
	struct aml_mbox_chan *aml_chan = mbox_chan->con_priv;

	return !readl(aml_chan->mbox_fsts_addr);
}

static struct mbox_chan_ops mbox_ops = {
	.send_data = mbox_transfer_data,
	.startup = mbox_startup,
	.shutdown = mbox_shutdown,
	.last_tx_done = mbox_last_tx_done,
};

static int mbox_fifo_parse_dt(struct platform_device *pdev, struct aml_chan_priv *priv)
{
	void __iomem *mbox_wr_base;
	void __iomem *mbox_rd_base;
	void __iomem *mbox_fset_base;
	void __iomem *mbox_fclr_base;
	void __iomem *mbox_fsts_base;
	void __iomem *mbox_irq_base;
	struct aml_mbox_chan *aml_chan;
	struct device *dev = &pdev->dev;
	unsigned int memidx = 0;
	u32 irqctlr = 0;
	u32 irqclr = 0;
	u32 irq_nums = 0;
	u32 mbox_nums = 0;
	u32 mboxid = 0;
	int idx = 0;
	int err = 0;

	mbox_wr_base = devm_platform_ioremap_resource(pdev, memidx++);
	if (IS_ERR_OR_NULL(mbox_wr_base))
		return PTR_ERR(mbox_wr_base);

	if (of_property_read_bool(dev->of_node, "mbox-wr-same")) {
		mbox_rd_base = mbox_wr_base;
	} else {
		mbox_rd_base = devm_platform_ioremap_resource(pdev, memidx++);
		if (IS_ERR_OR_NULL(mbox_rd_base))
			return PTR_ERR(mbox_rd_base);
	}
	mbox_fset_base = devm_platform_ioremap_resource(pdev, memidx++);
	if (IS_ERR(mbox_fset_base))
		return PTR_ERR(mbox_fset_base);

	mbox_fclr_base = devm_platform_ioremap_resource(pdev, memidx++);
	if (IS_ERR(mbox_fclr_base))
		return PTR_ERR(mbox_fclr_base);

	mbox_fsts_base = devm_platform_ioremap_resource(pdev, memidx++);
	if (IS_ERR(mbox_fsts_base))
		return PTR_ERR(mbox_fsts_base);

	mbox_irq_base = devm_platform_ioremap_resource(pdev, memidx++);
	if (IS_ERR(mbox_irq_base))
		return PTR_ERR(mbox_irq_base);

	err = of_property_read_u32(dev->of_node,
				   "mbox-irqctlr", &irqctlr);
	if (err) {
		dev_err(dev, "failed to get mbox irq ctlr %d\n", err);
		return -ENXIO;
	}
	err = of_property_read_u32(dev->of_node,
				   "mbox-irqclr", &irqclr);
	if (err)
		irqclr = irqctlr;

	err = of_property_read_u32(dev->of_node,
				   "mbox-irqnums", &irq_nums);
	if (err) {
		dev_err(dev, "set mbox irq_nums to default value\n");
		irq_nums = MHUIRQ_MAXNUM_DEF;
	}

	err = of_property_read_u32(dev->of_node,
				   "mbox-nums", &mbox_nums);
	if (err) {
		dev_err(dev, "failed to get mbox num %d\n", err);
		return -ENXIO;
	}
	priv->mbox_nums = mbox_nums;

	aml_chan = devm_kzalloc(dev, sizeof(*aml_chan) * mbox_nums, GFP_KERNEL);
	if (IS_ERR(aml_chan))
		return PTR_ERR(aml_chan);
	for (idx = 0; idx < mbox_nums; idx++) {
		err = of_property_read_u32_index(dev->of_node, "mboxids",
						 idx, &mboxid);
		if (err)
			return err;
		aml_chan[idx].mboxid = mboxid;
		aml_chan[idx].irq_nums = irq_nums;
		aml_chan[idx].mbox_wr_addr = mbox_wr_base + PAYLOAD_OFFSET(mboxid);
		aml_chan[idx].mbox_rd_addr = mbox_rd_base + PAYLOAD_OFFSET(mboxid);
		aml_chan[idx].mbox_fset_addr = mbox_fset_base + CTL_OFFSET(mboxid);
		aml_chan[idx].mbox_fclr_addr = mbox_fclr_base + CTL_OFFSET(mboxid);
		aml_chan[idx].mbox_fsts_addr = mbox_fsts_base + CTL_OFFSET(mboxid);
		aml_chan[idx].mbox_irqsts_addr = mbox_irq_base + IRQ_STS_OFFSET(irqctlr);
		aml_chan[idx].mbox_irqclr_addr = mbox_irq_base + IRQ_CLR_OFFSET(irqclr);
		mutex_init(&aml_chan[idx].mutex);
		aml_chan[idx].tx_complete = NULL;
	}
	priv->aml_chan = aml_chan;
	return 0;
}

static int mbox_fifo_probe(struct platform_device *pdev)
{
	struct mbox_chan *mbox_chans;
	struct device *dev = &pdev->dev;
	struct aml_mbox_chan *aml_chan;
	struct mbox_controller *mbox_cons;
	const struct mbox_domain_data *match;
	struct mbox_domain *mbox_domains;
	struct aml_chan_priv aml_priv;
	u32 mboxid = 0;
	u32 mbox_nums;
	int mbox_irq;
	int err = 0;
	int idx0 = 0;
	int idx1 = 0;

	dev_dbg(dev, "mbox fifo probe\n");

	err = mbox_fifo_parse_dt(pdev, &aml_priv);
	if (err) {
		dev_err(dev, "mbox parse dt fail\n");
		return err;
	}
	aml_chan = aml_priv.aml_chan;
	mbox_nums = aml_priv.mbox_nums;

	match = of_device_get_match_data(&pdev->dev);
	if (!match) {
		dev_err(&pdev->dev, "failed to get match data\n");
		return -ENODEV;
	}
	mbox_domains = match->mbox_domains;
	mbox_chans = devm_kzalloc(dev, sizeof(*mbox_chans) * match->domain_counts, GFP_KERNEL);
	if (IS_ERR(mbox_chans))
		return PTR_ERR(mbox_chans);

	for (idx0 = 0; idx0 < match->domain_counts; idx0++) {
		mboxid = mbox_domains[idx0].mboxid;
		for (idx1 = 0; idx1 < mbox_nums; idx1++) {
			if (mboxid == aml_chan[idx1].mboxid) {
				mbox_chans[idx0].con_priv = &aml_chan[idx1];
				break;
			}
		}
	}
	mbox_cons = devm_kzalloc(dev, sizeof(*mbox_cons), GFP_KERNEL);
	if (IS_ERR(mbox_cons))
		return PTR_ERR(mbox_cons);

	mbox_cons->chans = mbox_chans;
	mbox_cons->num_chans = match->domain_counts;
	mbox_cons->txdone_irq = true;
	mbox_cons->ops = &mbox_ops;
	mbox_cons->dev = dev;
	platform_set_drvdata(pdev, mbox_cons);
	if (mbox_controller_register(mbox_cons)) {
		dev_err(dev, "failed to register mailbox controller\n");
		return -ENOMEM;
	}

	for (idx0 = 0; idx0 < mbox_nums; idx0++)
		aml_chan[idx0].mbox = mbox_cons;

	mbox_irq = platform_get_irq(pdev, 0);
	if (mbox_irq < 0) {
		dev_err(dev, "failed to get interrupt %d\n", mbox_irq);
		return -ENXIO;
	}

	err = request_threaded_irq(mbox_irq, mbox_handler,
				   NULL, IRQF_ONESHOT | IRQF_NO_SUSPEND,
				   DRIVER_NAME, mbox_cons);
	if (err) {
		dev_err(dev, "request irq error\n");
		return err;
	}

	dev_dbg(dev, "mbox fifo probe done\n");
	return 0;
}

static int mbox_fifo_remove(struct platform_device *pdev)
{
	struct mbox_controller *mbox_cons = platform_get_drvdata(pdev);

	mbox_controller_unregister(mbox_cons);

	platform_set_drvdata(pdev, NULL);

	return 0;
}

struct mbox_domain sc2_mbox_domains[] = {
	[SC2_DSPA2REE0] = MBOX_DOMAIN(SC2_DSPA2REE0, SC2_MBOX_DSPA2REE, 0),
	[SC2_REE2DSPA0] = MBOX_DOMAIN(SC2_REE2DSPA0, SC2_MBOX_REE2DSPA, 0),
	[SC2_REE2DSPA1] = MBOX_DOMAIN(SC2_REE2DSPA1, SC2_MBOX_REE2DSPA, 0),
	[SC2_REE2DSPA2] = MBOX_DOMAIN(SC2_REE2DSPA2, SC2_MBOX_REE2DSPA, 0),
	[SC2_AO2REE]    = MBOX_DOMAIN(SC2_AO2REE, SC2_MBOX_AO2REE, 0),
	[SC2_REE2AO0]   = MBOX_DOMAIN(SC2_REE2AO0, SC2_MBOX_REE2AO, 0),
	[SC2_REE2AO1]   = MBOX_DOMAIN(SC2_REE2AO1, SC2_MBOX_REE2AO, 0),
	[SC2_REE2AO2]   = MBOX_DOMAIN(SC2_REE2AO2, SC2_MBOX_REE2AO, 0),
	[SC2_REE2AO3]   = MBOX_DOMAIN(SC2_REE2AO3, SC2_MBOX_REE2AO, 0),
	[SC2_REE2AO4]   = MBOX_DOMAIN(SC2_REE2AO4, SC2_MBOX_REE2AO, 0),
	[SC2_REE2AO5]   = MBOX_DOMAIN(SC2_REE2AO5, SC2_MBOX_REE2AO, 0),
};

static struct mbox_domain_data sc2_mbox_domains_data __initdata = {
	.mbox_domains = sc2_mbox_domains,
	.domain_counts = ARRAY_SIZE(sc2_mbox_domains),
};

struct mbox_domain t3_mbox_domains[] = {
	[T3_DSPA2REE0] = MBOX_DOMAIN(T3_DSPA2REE0, T3_MBOX_DSPA2REE, 0),
	[T3_REE2DSPA0] = MBOX_DOMAIN(T3_REE2DSPA0, T3_MBOX_REE2DSPA, 0),
	[T3_REE2DSPA1] = MBOX_DOMAIN(T3_REE2DSPA1, T3_MBOX_REE2DSPA, 0),
	[T3_REE2DSPA2] = MBOX_DOMAIN(T3_REE2DSPA2, T3_MBOX_REE2DSPA, 0),
	[T3_AO2REE]    = MBOX_DOMAIN(T3_AO2REE, T3_MBOX_AO2REE, 0),
	[T3_REE2AO0]   = MBOX_DOMAIN(T3_REE2AO0, T3_MBOX_REE2AO, 0),
	[T3_REE2AO1]   = MBOX_DOMAIN(T3_REE2AO1, T3_MBOX_REE2AO, 0),
	[T3_REE2AO2]   = MBOX_DOMAIN(T3_REE2AO2, T3_MBOX_REE2AO, 0),
	[T3_REE2AO3]   = MBOX_DOMAIN(T3_REE2AO3, T3_MBOX_REE2AO, 0),
	[T3_REE2AO4]   = MBOX_DOMAIN(T3_REE2AO4, T3_MBOX_REE2AO, 0),
	[T3_REE2AO5]   = MBOX_DOMAIN(T3_REE2AO5, T3_MBOX_REE2AO, 0),
};

static struct mbox_domain_data t3_mbox_domains_data __initdata = {
	.mbox_domains = t3_mbox_domains,
	.domain_counts = ARRAY_SIZE(t3_mbox_domains),
};

struct mbox_domain t7_mbox_domains[] = {
	[T7_DSPA2REE0] = MBOX_DOMAIN(T7_DSPA2REE0, T7_MBOX_DSPA2REE, 0),
	[T7_REE2DSPA0] = MBOX_DOMAIN(T7_REE2DSPA0, T7_MBOX_REE2DSPA, 0),
	[T7_REE2DSPA1] = MBOX_DOMAIN(T7_REE2DSPA1, T7_MBOX_REE2DSPA, 0),
	[T7_REE2DSPA2] = MBOX_DOMAIN(T7_REE2DSPA2, T7_MBOX_REE2DSPA, 0),
	[T7_AO2REE]    = MBOX_DOMAIN(T7_AO2REE, T7_MBOX_AO2REE, 0),
	[T7_REE2AO0]   = MBOX_DOMAIN(T7_REE2AO0, T7_MBOX_REE2AO, 0),
	[T7_REE2AO1]   = MBOX_DOMAIN(T7_REE2AO1, T7_MBOX_REE2AO, 0),
	[T7_REE2AO2]   = MBOX_DOMAIN(T7_REE2AO2, T7_MBOX_REE2AO, 0),
	[T7_REE2AO3]   = MBOX_DOMAIN(T7_REE2AO3, T7_MBOX_REE2AO, 0),
	[T7_REE2AO4]   = MBOX_DOMAIN(T7_REE2AO4, T7_MBOX_REE2AO, 0),
	[T7_REE2AO5]   = MBOX_DOMAIN(T7_REE2AO5, T7_MBOX_REE2AO, 0),
	[T7_DSPB2REE0] = MBOX_DOMAIN(T7_DSPB2REE0, T7_MBOX_DSPB2REE, 0),
	[T7_REE2DSPB0] = MBOX_DOMAIN(T7_REE2DSPB0, T7_MBOX_REE2DSPB, 0),
	[T7_REE2DSPB1] = MBOX_DOMAIN(T7_REE2DSPB1, T7_MBOX_REE2DSPB, 0),
	[T7_REE2DSPB2] = MBOX_DOMAIN(T7_REE2DSPB2, T7_MBOX_REE2DSPB, 0),
};

static struct mbox_domain_data t7_mbox_domains_data __initdata = {
	.mbox_domains = t7_mbox_domains,
	.domain_counts = ARRAY_SIZE(t7_mbox_domains),
};

struct mbox_domain s4_mbox_domains[] = {
	[S4_AO2REE]    = MBOX_DOMAIN(S4_AO2REE, S4_MBOX_AO2REE, 0),
	[S4_REE2AO0]   = MBOX_DOMAIN(S4_REE2AO0, S4_MBOX_REE2AO, 0),
	[S4_REE2AO1]   = MBOX_DOMAIN(S4_REE2AO1, S4_MBOX_REE2AO, 0),
	[S4_REE2AO2]   = MBOX_DOMAIN(S4_REE2AO2, S4_MBOX_REE2AO, 0),
	[S4_REE2AO3]   = MBOX_DOMAIN(S4_REE2AO3, S4_MBOX_REE2AO, 0),
	[S4_REE2AO4]   = MBOX_DOMAIN(S4_REE2AO4, S4_MBOX_REE2AO, 0),
	[S4_REE2AO5]   = MBOX_DOMAIN(S4_REE2AO5, S4_MBOX_REE2AO, 0),
};

static struct mbox_domain_data s4_mbox_domains_data __initdata = {
	.mbox_domains = s4_mbox_domains,
	.domain_counts = ARRAY_SIZE(s4_mbox_domains),
};

struct mbox_domain t5m_mbox_domains[] = {
	[T5M_AO2REE]    = MBOX_DOMAIN(T5M_AO2REE, T5M_MBOX_AO2REE, 0),
	[T5M_REE2AO0]   = MBOX_DOMAIN(T5M_REE2AO0, T5M_MBOX_REE2AO, 0),
	[T5M_REE2AO1]   = MBOX_DOMAIN(T5M_REE2AO1, T5M_MBOX_REE2AO, 0),
	[T5M_REE2AO2]   = MBOX_DOMAIN(T5M_REE2AO2, T5M_MBOX_REE2AO, 0),
	[T5M_REE2AO3]   = MBOX_DOMAIN(T5M_REE2AO3, T5M_MBOX_REE2AO, 0),
	[T5M_REE2AO4]   = MBOX_DOMAIN(T5M_REE2AO4, T5M_MBOX_REE2AO, 0),
	[T5M_REE2AO5]   = MBOX_DOMAIN(T5M_REE2AO5, T5M_MBOX_REE2AO, 0),
};

static struct mbox_domain_data t5m_mbox_domains_data __initdata = {
	.mbox_domains = t5m_mbox_domains,
	.domain_counts = ARRAY_SIZE(t5m_mbox_domains),
};

struct mbox_domain t3x_mbox_domains[] = {
	[T3X_DSPA2REE0] = MBOX_DOMAIN(T3X_DSPA2REE0, T3X_MBOX_DSPA2REE, 0),
	[T3X_REE2DSPA0] = MBOX_DOMAIN(T3X_REE2DSPA0, T3X_MBOX_REE2DSPA, 0),
	[T3X_REE2DSPA1] = MBOX_DOMAIN(T3X_REE2DSPA1, T3X_MBOX_REE2DSPA, 0),
	[T3X_REE2DSPA2] = MBOX_DOMAIN(T3X_REE2DSPA2, T3X_MBOX_REE2DSPA, 0),
	[T3X_AO2REE]    = MBOX_DOMAIN(T3X_AO2REE, T3X_MBOX_AO2REE, 0),
	[T3X_REE2AO0]   = MBOX_DOMAIN(T3X_REE2AO0, T3X_MBOX_REE2AO, 0),
	[T3X_REE2AO1]   = MBOX_DOMAIN(T3X_REE2AO1, T3X_MBOX_REE2AO, 0),
	[T3X_REE2AO2]   = MBOX_DOMAIN(T3X_REE2AO2, T3X_MBOX_REE2AO, 0),
	[T3X_REE2AO3]   = MBOX_DOMAIN(T3X_REE2AO3, T3X_MBOX_REE2AO, 0),
	[T3X_REE2AO4]   = MBOX_DOMAIN(T3X_REE2AO4, T3X_MBOX_REE2AO, 0),
	[T3X_REE2AO5]   = MBOX_DOMAIN(T3X_REE2AO5, T3X_MBOX_REE2AO, 0),
};

static struct mbox_domain_data t3x_mbox_domains_data __initdata = {
	.mbox_domains = t3x_mbox_domains,
	.domain_counts = ARRAY_SIZE(t3x_mbox_domains),
};

struct mbox_domain s5_mbox_domains[] = {
	[S5_AO2REE]    = MBOX_DOMAIN(S5_AO2REE, S5_MBOX_AO2REE, 0),
	[S5_REE2AO0]   = MBOX_DOMAIN(S5_REE2AO0, S5_MBOX_REE2AO, 0),
	[S5_REE2AO1]   = MBOX_DOMAIN(S5_REE2AO1, S5_MBOX_REE2AO, 0),
	[S5_REE2AO2]   = MBOX_DOMAIN(S5_REE2AO2, S5_MBOX_REE2AO, 0),
	[S5_REE2AO3]   = MBOX_DOMAIN(S5_REE2AO3, S5_MBOX_REE2AO, 0),
	[S5_REE2AO4]   = MBOX_DOMAIN(S5_REE2AO4, S5_MBOX_REE2AO, 0),
	[S5_REE2AO5]   = MBOX_DOMAIN(S5_REE2AO5, S5_MBOX_REE2AO, 0),
};

static struct mbox_domain_data s5_mbox_domains_data __initdata = {
	.mbox_domains = s5_mbox_domains,
	.domain_counts = ARRAY_SIZE(s5_mbox_domains),
};

struct mbox_domain s1a_mbox_domains[] = {
	[S1A_AO2REE]    = MBOX_DOMAIN(S1A_AO2REE, S1A_MBOX_AO2REE, 0),
	[S1A_REE2AO0]   = MBOX_DOMAIN(S1A_REE2AO0, S1A_MBOX_REE2AO, 0),
	[S1A_REE2AO1]   = MBOX_DOMAIN(S1A_REE2AO1, S1A_MBOX_REE2AO, 0),
	[S1A_REE2AO2]   = MBOX_DOMAIN(S1A_REE2AO2, S1A_MBOX_REE2AO, 0),
	[S1A_REE2AO3]   = MBOX_DOMAIN(S1A_REE2AO3, S1A_MBOX_REE2AO, 0),
	[S1A_REE2AO4]   = MBOX_DOMAIN(S1A_REE2AO4, S1A_MBOX_REE2AO, 0),
	[S1A_REE2AO5]   = MBOX_DOMAIN(S1A_REE2AO5, S1A_MBOX_REE2AO, 0),
};

static struct mbox_domain_data s1a_mbox_domains_data __initdata = {
	.mbox_domains = s1a_mbox_domains,
	.domain_counts = ARRAY_SIZE(s1a_mbox_domains),
};

static const struct of_device_id mbox_of_match[] = {
	{
		.compatible = "amlogic, t3-mbox-fifo",
		.data = &t3_mbox_domains_data,
	},
	{
		.compatible = "amlogic, t7-mbox-fifo",
		.data = &t7_mbox_domains_data,
	},
	{
		.compatible = "amlogic, sc2-mbox-fifo",
		.data = &sc2_mbox_domains_data,
	},
	{
		.compatible = "amlogic, s4-mbox-fifo",
		.data = &s4_mbox_domains_data,
	},
	{
		.compatible = "amlogic, t5m-mbox-fifo",
		.data = &t5m_mbox_domains_data,
	},
	{
		.compatible = "amlogic, t3x-mbox-fifo",
		.data = &t3x_mbox_domains_data,
	},
	{
		.compatible = "amlogic, s5-mbox-fifo",
		.data = &s5_mbox_domains_data,
	},
	{
		.compatible = "amlogic, s1a-mbox-fifo",
		.data = &s1a_mbox_domains_data,
	},
	{},
};

static struct platform_driver mbox_fifo_driver = {
	.probe = mbox_fifo_probe,
	.remove = mbox_fifo_remove,
	.driver = {
		.owner	= THIS_MODULE,
		.name   = DRIVER_NAME,
		.of_match_table = mbox_of_match,
	},
};

int __init mbox_fifo_init(void)
{
	return platform_driver_register(&mbox_fifo_driver);
}

void __exit mbox_fifo_exit(void)
{
	platform_driver_unregister(&mbox_fifo_driver);
}
