| // SPDX-License-Identifier: GPL-2.0 | 
 | // Copyright (c) 2017-2018 Hisilicon Limited. | 
 | // Copyright (c) 2017-2018 Linaro Limited. | 
 |  | 
 | #include <linux/bitops.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/device.h> | 
 | #include <linux/err.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/io.h> | 
 | #include <linux/iopoll.h> | 
 | #include <linux/mailbox_controller.h> | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include "mailbox.h" | 
 |  | 
 | #define MBOX_CHAN_MAX			32 | 
 |  | 
 | #define MBOX_RX				0x0 | 
 | #define MBOX_TX				0x1 | 
 |  | 
 | #define MBOX_BASE(mbox, ch)		((mbox)->base + ((ch) * 0x40)) | 
 | #define MBOX_SRC_REG			0x00 | 
 | #define MBOX_DST_REG			0x04 | 
 | #define MBOX_DCLR_REG			0x08 | 
 | #define MBOX_DSTAT_REG			0x0c | 
 | #define MBOX_MODE_REG			0x10 | 
 | #define MBOX_IMASK_REG			0x14 | 
 | #define MBOX_ICLR_REG			0x18 | 
 | #define MBOX_SEND_REG			0x1c | 
 | #define MBOX_DATA_REG			0x20 | 
 |  | 
 | #define MBOX_IPC_LOCK_REG		0xa00 | 
 | #define MBOX_IPC_UNLOCK			0x1acce551 | 
 |  | 
 | #define MBOX_AUTOMATIC_ACK		1 | 
 |  | 
 | #define MBOX_STATE_IDLE			BIT(4) | 
 | #define MBOX_STATE_ACK			BIT(7) | 
 |  | 
 | #define MBOX_MSG_LEN			8 | 
 |  | 
 | /** | 
 |  * Hi3660 mailbox channel information | 
 |  * | 
 |  * A channel can be used for TX or RX, it can trigger remote | 
 |  * processor interrupt to notify remote processor and can receive | 
 |  * interrupt if has incoming message. | 
 |  * | 
 |  * @dst_irq:	Interrupt vector for remote processor | 
 |  * @ack_irq:	Interrupt vector for local processor | 
 |  */ | 
 | struct hi3660_chan_info { | 
 | 	unsigned int dst_irq; | 
 | 	unsigned int ack_irq; | 
 | }; | 
 |  | 
 | /** | 
 |  * Hi3660 mailbox controller data | 
 |  * | 
 |  * Mailbox controller includes 32 channels and can allocate | 
 |  * channel for message transferring. | 
 |  * | 
 |  * @dev:	Device to which it is attached | 
 |  * @base:	Base address of the register mapping region | 
 |  * @chan:	Representation of channels in mailbox controller | 
 |  * @mchan:	Representation of channel info | 
 |  * @controller:	Representation of a communication channel controller | 
 |  */ | 
 | struct hi3660_mbox { | 
 | 	struct device *dev; | 
 | 	void __iomem *base; | 
 | 	struct mbox_chan chan[MBOX_CHAN_MAX]; | 
 | 	struct hi3660_chan_info mchan[MBOX_CHAN_MAX]; | 
 | 	struct mbox_controller controller; | 
 | }; | 
 |  | 
 | static struct hi3660_mbox *to_hi3660_mbox(struct mbox_controller *mbox) | 
 | { | 
 | 	return container_of(mbox, struct hi3660_mbox, controller); | 
 | } | 
 |  | 
 | static int hi3660_mbox_check_state(struct mbox_chan *chan) | 
 | { | 
 | 	unsigned long ch = (unsigned long)chan->con_priv; | 
 | 	struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox); | 
 | 	struct hi3660_chan_info *mchan = &mbox->mchan[ch]; | 
 | 	void __iomem *base = MBOX_BASE(mbox, ch); | 
 | 	unsigned long val; | 
 | 	unsigned int ret; | 
 |  | 
 | 	/* Mailbox is idle so directly bail out */ | 
 | 	if (readl(base + MBOX_MODE_REG) & MBOX_STATE_IDLE) | 
 | 		return 0; | 
 |  | 
 | 	/* Wait for acknowledge from remote */ | 
 | 	ret = readx_poll_timeout_atomic(readl, base + MBOX_MODE_REG, | 
 | 			val, (val & MBOX_STATE_ACK), 1000, 300000); | 
 | 	if (ret) { | 
 | 		dev_err(mbox->dev, "%s: timeout for receiving ack\n", __func__); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	/* Ensure channel is released */ | 
 | 	writel(0xffffffff, base + MBOX_IMASK_REG); | 
 | 	writel(BIT(mchan->ack_irq), base + MBOX_SRC_REG); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hi3660_mbox_unlock(struct mbox_chan *chan) | 
 | { | 
 | 	struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox); | 
 | 	unsigned int val, retry = 3; | 
 |  | 
 | 	do { | 
 | 		writel(MBOX_IPC_UNLOCK, mbox->base + MBOX_IPC_LOCK_REG); | 
 |  | 
 | 		val = readl(mbox->base + MBOX_IPC_LOCK_REG); | 
 | 		if (!val) | 
 | 			break; | 
 |  | 
 | 		udelay(10); | 
 | 	} while (retry--); | 
 |  | 
 | 	if (val) | 
 | 		dev_err(mbox->dev, "%s: failed to unlock mailbox\n", __func__); | 
 |  | 
 | 	return (!val) ? 0 : -ETIMEDOUT; | 
 | } | 
 |  | 
 | static int hi3660_mbox_acquire_channel(struct mbox_chan *chan) | 
 | { | 
 | 	unsigned long ch = (unsigned long)chan->con_priv; | 
 | 	struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox); | 
 | 	struct hi3660_chan_info *mchan = &mbox->mchan[ch]; | 
 | 	void __iomem *base = MBOX_BASE(mbox, ch); | 
 | 	unsigned int val, retry; | 
 |  | 
 | 	for (retry = 10; retry; retry--) { | 
 | 		/* Check if channel is in idle state */ | 
 | 		if (readl(base + MBOX_MODE_REG) & MBOX_STATE_IDLE) { | 
 | 			writel(BIT(mchan->ack_irq), base + MBOX_SRC_REG); | 
 |  | 
 | 			/* Check ack bit has been set successfully */ | 
 | 			val = readl(base + MBOX_SRC_REG); | 
 | 			if (val & BIT(mchan->ack_irq)) | 
 | 				break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!retry) | 
 | 		dev_err(mbox->dev, "%s: failed to acquire channel\n", __func__); | 
 |  | 
 | 	return retry ? 0 : -ETIMEDOUT; | 
 | } | 
 |  | 
 | static int hi3660_mbox_startup(struct mbox_chan *chan) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = hi3660_mbox_check_state(chan); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = hi3660_mbox_unlock(chan); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = hi3660_mbox_acquire_channel(chan); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hi3660_mbox_send_data(struct mbox_chan *chan, void *msg) | 
 | { | 
 | 	unsigned long ch = (unsigned long)chan->con_priv; | 
 | 	struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox); | 
 | 	struct hi3660_chan_info *mchan = &mbox->mchan[ch]; | 
 | 	void __iomem *base = MBOX_BASE(mbox, ch); | 
 | 	u32 *buf = msg; | 
 | 	unsigned int i; | 
 |  | 
 | 	/* Ensure channel is released */ | 
 | 	writel_relaxed(0xffffffff, base + MBOX_IMASK_REG); | 
 | 	writel_relaxed(BIT(mchan->ack_irq), base + MBOX_SRC_REG); | 
 |  | 
 | 	/* Clear mask for destination interrupt */ | 
 | 	writel_relaxed(~BIT(mchan->dst_irq), base + MBOX_IMASK_REG); | 
 |  | 
 | 	/* Config destination for interrupt vector */ | 
 | 	writel_relaxed(BIT(mchan->dst_irq), base + MBOX_DST_REG); | 
 |  | 
 | 	/* Automatic acknowledge mode */ | 
 | 	writel_relaxed(MBOX_AUTOMATIC_ACK, base + MBOX_MODE_REG); | 
 |  | 
 | 	/* Fill message data */ | 
 | 	for (i = 0; i < MBOX_MSG_LEN; i++) | 
 | 		writel_relaxed(buf[i], base + MBOX_DATA_REG + i * 4); | 
 |  | 
 | 	/* Trigger data transferring */ | 
 | 	writel(BIT(mchan->ack_irq), base + MBOX_SEND_REG); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct mbox_chan_ops hi3660_mbox_ops = { | 
 | 	.startup	= hi3660_mbox_startup, | 
 | 	.send_data	= hi3660_mbox_send_data, | 
 | }; | 
 |  | 
 | static struct mbox_chan *hi3660_mbox_xlate(struct mbox_controller *controller, | 
 | 					   const struct of_phandle_args *spec) | 
 | { | 
 | 	struct hi3660_mbox *mbox = to_hi3660_mbox(controller); | 
 | 	struct hi3660_chan_info *mchan; | 
 | 	unsigned int ch = spec->args[0]; | 
 |  | 
 | 	if (ch >= MBOX_CHAN_MAX) { | 
 | 		dev_err(mbox->dev, "Invalid channel idx %d\n", ch); | 
 | 		return ERR_PTR(-EINVAL); | 
 | 	} | 
 |  | 
 | 	mchan = &mbox->mchan[ch]; | 
 | 	mchan->dst_irq = spec->args[1]; | 
 | 	mchan->ack_irq = spec->args[2]; | 
 |  | 
 | 	return &mbox->chan[ch]; | 
 | } | 
 |  | 
 | static const struct of_device_id hi3660_mbox_of_match[] = { | 
 | 	{ .compatible = "hisilicon,hi3660-mbox", }, | 
 | 	{}, | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(of, hi3660_mbox_of_match); | 
 |  | 
 | static int hi3660_mbox_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct hi3660_mbox *mbox; | 
 | 	struct mbox_chan *chan; | 
 | 	struct resource *res; | 
 | 	unsigned long ch; | 
 | 	int err; | 
 |  | 
 | 	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); | 
 | 	if (!mbox) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	mbox->base = devm_ioremap_resource(dev, res); | 
 | 	if (IS_ERR(mbox->base)) | 
 | 		return PTR_ERR(mbox->base); | 
 |  | 
 | 	mbox->dev = dev; | 
 | 	mbox->controller.dev = dev; | 
 | 	mbox->controller.chans = mbox->chan; | 
 | 	mbox->controller.num_chans = MBOX_CHAN_MAX; | 
 | 	mbox->controller.ops = &hi3660_mbox_ops; | 
 | 	mbox->controller.of_xlate = hi3660_mbox_xlate; | 
 |  | 
 | 	/* Initialize mailbox channel data */ | 
 | 	chan = mbox->chan; | 
 | 	for (ch = 0; ch < MBOX_CHAN_MAX; ch++) | 
 | 		chan[ch].con_priv = (void *)ch; | 
 |  | 
 | 	err = mbox_controller_register(&mbox->controller); | 
 | 	if (err) { | 
 | 		dev_err(dev, "Failed to register mailbox %d\n", err); | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	platform_set_drvdata(pdev, mbox); | 
 | 	dev_info(dev, "Mailbox enabled\n"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hi3660_mbox_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct hi3660_mbox *mbox = platform_get_drvdata(pdev); | 
 |  | 
 | 	mbox_controller_unregister(&mbox->controller); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct platform_driver hi3660_mbox_driver = { | 
 | 	.probe  = hi3660_mbox_probe, | 
 | 	.remove = hi3660_mbox_remove, | 
 | 	.driver = { | 
 | 		.name = "hi3660-mbox", | 
 | 		.of_match_table = hi3660_mbox_of_match, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init hi3660_mbox_init(void) | 
 | { | 
 | 	return platform_driver_register(&hi3660_mbox_driver); | 
 | } | 
 | core_initcall(hi3660_mbox_init); | 
 |  | 
 | static void __exit hi3660_mbox_exit(void) | 
 | { | 
 | 	platform_driver_unregister(&hi3660_mbox_driver); | 
 | } | 
 | module_exit(hi3660_mbox_exit); | 
 |  | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_DESCRIPTION("Hisilicon Hi3660 Mailbox Controller"); | 
 | MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>"); |