162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * APM X-Gene SLIMpro MailBox Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2015, Applied Micro Circuits Corporation
662306a36Sopenharmony_ci * Author: Feng Kan fkan@apm.com
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci#include <linux/acpi.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/interrupt.h>
1162306a36Sopenharmony_ci#include <linux/io.h>
1262306a36Sopenharmony_ci#include <linux/mailbox_controller.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/spinlock.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define MBOX_CON_NAME			"slimpro-mbox"
1962306a36Sopenharmony_ci#define MBOX_REG_SET_OFFSET		0x1000
2062306a36Sopenharmony_ci#define MBOX_CNT			8
2162306a36Sopenharmony_ci#define MBOX_STATUS_AVAIL_MASK		BIT(16)
2262306a36Sopenharmony_ci#define MBOX_STATUS_ACK_MASK		BIT(0)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* Configuration and Status Registers */
2562306a36Sopenharmony_ci#define REG_DB_IN		0x00
2662306a36Sopenharmony_ci#define REG_DB_DIN0		0x04
2762306a36Sopenharmony_ci#define REG_DB_DIN1		0x08
2862306a36Sopenharmony_ci#define REG_DB_OUT		0x10
2962306a36Sopenharmony_ci#define REG_DB_DOUT0		0x14
3062306a36Sopenharmony_ci#define REG_DB_DOUT1		0x18
3162306a36Sopenharmony_ci#define REG_DB_STAT		0x20
3262306a36Sopenharmony_ci#define REG_DB_STATMASK		0x24
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/**
3562306a36Sopenharmony_ci * X-Gene SlimPRO mailbox channel information
3662306a36Sopenharmony_ci *
3762306a36Sopenharmony_ci * @dev:	Device to which it is attached
3862306a36Sopenharmony_ci * @chan:	Pointer to mailbox communication channel
3962306a36Sopenharmony_ci * @reg:	Base address to access channel registers
4062306a36Sopenharmony_ci * @irq:	Interrupt number of the channel
4162306a36Sopenharmony_ci * @rx_msg:	Received message storage
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_cistruct slimpro_mbox_chan {
4462306a36Sopenharmony_ci	struct device		*dev;
4562306a36Sopenharmony_ci	struct mbox_chan	*chan;
4662306a36Sopenharmony_ci	void __iomem		*reg;
4762306a36Sopenharmony_ci	int			irq;
4862306a36Sopenharmony_ci	u32			rx_msg[3];
4962306a36Sopenharmony_ci};
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/**
5262306a36Sopenharmony_ci * X-Gene SlimPRO Mailbox controller data
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * X-Gene SlimPRO Mailbox controller has 8 communication channels.
5562306a36Sopenharmony_ci * Each channel has a separate IRQ number assigned to it.
5662306a36Sopenharmony_ci *
5762306a36Sopenharmony_ci * @mb_ctrl:	Representation of the communication channel controller
5862306a36Sopenharmony_ci * @mc:		Array of SlimPRO mailbox channels of the controller
5962306a36Sopenharmony_ci * @chans:	Array of mailbox communication channels
6062306a36Sopenharmony_ci *
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_cistruct slimpro_mbox {
6362306a36Sopenharmony_ci	struct mbox_controller		mb_ctrl;
6462306a36Sopenharmony_ci	struct slimpro_mbox_chan	mc[MBOX_CNT];
6562306a36Sopenharmony_ci	struct mbox_chan		chans[MBOX_CNT];
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	writel(msg[1], mb_chan->reg + REG_DB_DOUT0);
7162306a36Sopenharmony_ci	writel(msg[2], mb_chan->reg + REG_DB_DOUT1);
7262306a36Sopenharmony_ci	writel(msg[0], mb_chan->reg + REG_DB_OUT);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	mb_chan->rx_msg[1] = readl(mb_chan->reg + REG_DB_DIN0);
7862306a36Sopenharmony_ci	mb_chan->rx_msg[2] = readl(mb_chan->reg + REG_DB_DIN1);
7962306a36Sopenharmony_ci	mb_chan->rx_msg[0] = readl(mb_chan->reg + REG_DB_IN);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	u32 val = readl(mb_chan->reg + REG_DB_STAT);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (val & MBOX_STATUS_ACK_MASK) {
8762306a36Sopenharmony_ci		writel(MBOX_STATUS_ACK_MASK, mb_chan->reg + REG_DB_STAT);
8862306a36Sopenharmony_ci		return 1;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci	return 0;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	u32 val = readl(mb_chan->reg + REG_DB_STAT);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (val & MBOX_STATUS_AVAIL_MASK) {
9862306a36Sopenharmony_ci		mb_chan_recv_msg(mb_chan);
9962306a36Sopenharmony_ci		writel(MBOX_STATUS_AVAIL_MASK, mb_chan->reg + REG_DB_STAT);
10062306a36Sopenharmony_ci		return 1;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci	return 0;
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic irqreturn_t slimpro_mbox_irq(int irq, void *id)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct slimpro_mbox_chan *mb_chan = id;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (mb_chan_status_ack(mb_chan))
11062306a36Sopenharmony_ci		mbox_chan_txdone(mb_chan->chan, 0);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (mb_chan_status_avail(mb_chan))
11362306a36Sopenharmony_ci		mbox_chan_received_data(mb_chan->chan, mb_chan->rx_msg);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return IRQ_HANDLED;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct slimpro_mbox_chan *mb_chan = chan->con_priv;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	mb_chan_send_msg(mb_chan, msg);
12362306a36Sopenharmony_ci	return 0;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int slimpro_mbox_startup(struct mbox_chan *chan)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct slimpro_mbox_chan *mb_chan = chan->con_priv;
12962306a36Sopenharmony_ci	int rc;
13062306a36Sopenharmony_ci	u32 val;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	rc = devm_request_irq(mb_chan->dev, mb_chan->irq, slimpro_mbox_irq, 0,
13362306a36Sopenharmony_ci			      MBOX_CON_NAME, mb_chan);
13462306a36Sopenharmony_ci	if (unlikely(rc)) {
13562306a36Sopenharmony_ci		dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n",
13662306a36Sopenharmony_ci			mb_chan->irq);
13762306a36Sopenharmony_ci		return rc;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Enable HW interrupt */
14162306a36Sopenharmony_ci	writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK,
14262306a36Sopenharmony_ci	       mb_chan->reg + REG_DB_STAT);
14362306a36Sopenharmony_ci	/* Unmask doorbell status interrupt */
14462306a36Sopenharmony_ci	val = readl(mb_chan->reg + REG_DB_STATMASK);
14562306a36Sopenharmony_ci	val &= ~(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK);
14662306a36Sopenharmony_ci	writel(val, mb_chan->reg + REG_DB_STATMASK);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic void slimpro_mbox_shutdown(struct mbox_chan *chan)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct slimpro_mbox_chan *mb_chan = chan->con_priv;
15462306a36Sopenharmony_ci	u32 val;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Mask doorbell status interrupt */
15762306a36Sopenharmony_ci	val = readl(mb_chan->reg + REG_DB_STATMASK);
15862306a36Sopenharmony_ci	val |= (MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK);
15962306a36Sopenharmony_ci	writel(val, mb_chan->reg + REG_DB_STATMASK);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	devm_free_irq(mb_chan->dev, mb_chan->irq, mb_chan);
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic const struct mbox_chan_ops slimpro_mbox_ops = {
16562306a36Sopenharmony_ci	.send_data = slimpro_mbox_send_data,
16662306a36Sopenharmony_ci	.startup = slimpro_mbox_startup,
16762306a36Sopenharmony_ci	.shutdown = slimpro_mbox_shutdown,
16862306a36Sopenharmony_ci};
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic int slimpro_mbox_probe(struct platform_device *pdev)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct slimpro_mbox *ctx;
17362306a36Sopenharmony_ci	void __iomem *mb_base;
17462306a36Sopenharmony_ci	int rc;
17562306a36Sopenharmony_ci	int i;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	ctx = devm_kzalloc(&pdev->dev, sizeof(struct slimpro_mbox), GFP_KERNEL);
17862306a36Sopenharmony_ci	if (!ctx)
17962306a36Sopenharmony_ci		return -ENOMEM;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	platform_set_drvdata(pdev, ctx);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	mb_base = devm_platform_ioremap_resource(pdev, 0);
18462306a36Sopenharmony_ci	if (IS_ERR(mb_base))
18562306a36Sopenharmony_ci		return PTR_ERR(mb_base);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/* Setup mailbox links */
18862306a36Sopenharmony_ci	for (i = 0; i < MBOX_CNT; i++) {
18962306a36Sopenharmony_ci		ctx->mc[i].irq = platform_get_irq(pdev, i);
19062306a36Sopenharmony_ci		if (ctx->mc[i].irq < 0) {
19162306a36Sopenharmony_ci			if (i == 0) {
19262306a36Sopenharmony_ci				dev_err(&pdev->dev, "no available IRQ\n");
19362306a36Sopenharmony_ci				return -EINVAL;
19462306a36Sopenharmony_ci			}
19562306a36Sopenharmony_ci			dev_info(&pdev->dev, "no IRQ for channel %d\n", i);
19662306a36Sopenharmony_ci			break;
19762306a36Sopenharmony_ci		}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci		ctx->mc[i].dev = &pdev->dev;
20062306a36Sopenharmony_ci		ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET;
20162306a36Sopenharmony_ci		ctx->mc[i].chan = &ctx->chans[i];
20262306a36Sopenharmony_ci		ctx->chans[i].con_priv = &ctx->mc[i];
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/* Setup mailbox controller */
20662306a36Sopenharmony_ci	ctx->mb_ctrl.dev = &pdev->dev;
20762306a36Sopenharmony_ci	ctx->mb_ctrl.chans = ctx->chans;
20862306a36Sopenharmony_ci	ctx->mb_ctrl.txdone_irq = true;
20962306a36Sopenharmony_ci	ctx->mb_ctrl.ops = &slimpro_mbox_ops;
21062306a36Sopenharmony_ci	ctx->mb_ctrl.num_chans = i;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	rc = devm_mbox_controller_register(&pdev->dev, &ctx->mb_ctrl);
21362306a36Sopenharmony_ci	if (rc) {
21462306a36Sopenharmony_ci		dev_err(&pdev->dev,
21562306a36Sopenharmony_ci			"APM X-Gene SLIMpro MailBox register failed:%d\n", rc);
21662306a36Sopenharmony_ci		return rc;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n");
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic const struct of_device_id slimpro_of_match[] = {
22462306a36Sopenharmony_ci	{.compatible = "apm,xgene-slimpro-mbox" },
22562306a36Sopenharmony_ci	{ },
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, slimpro_of_match);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci#ifdef CONFIG_ACPI
23062306a36Sopenharmony_cistatic const struct acpi_device_id slimpro_acpi_ids[] = {
23162306a36Sopenharmony_ci	{"APMC0D01", 0},
23262306a36Sopenharmony_ci	{}
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids);
23562306a36Sopenharmony_ci#endif
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic struct platform_driver slimpro_mbox_driver = {
23862306a36Sopenharmony_ci	.probe	= slimpro_mbox_probe,
23962306a36Sopenharmony_ci	.driver	= {
24062306a36Sopenharmony_ci		.name = "xgene-slimpro-mbox",
24162306a36Sopenharmony_ci		.of_match_table = of_match_ptr(slimpro_of_match),
24262306a36Sopenharmony_ci		.acpi_match_table = ACPI_PTR(slimpro_acpi_ids)
24362306a36Sopenharmony_ci	},
24462306a36Sopenharmony_ci};
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic int __init slimpro_mbox_init(void)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	return platform_driver_register(&slimpro_mbox_driver);
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic void __exit slimpro_mbox_exit(void)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	platform_driver_unregister(&slimpro_mbox_driver);
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cisubsys_initcall(slimpro_mbox_init);
25762306a36Sopenharmony_cimodule_exit(slimpro_mbox_exit);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ciMODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver");
26062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
261