| // 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_READY BIT(5) |
| #define MBOX_STATE_ACK BIT(7) |
| |
| #define MBOX_MSG_LEN 8 |
| |
| /** |
| * struct hi3660_chan_info - Hi3660 mailbox channel information |
| * @dst_irq: Interrupt vector for remote processor |
| * @ack_irq: Interrupt vector for local processor |
| * |
| * A channel can be used for TX or RX, it can trigger remote |
| * processor interrupt to notify remote processor and can receive |
| * interrupt if it has an incoming message. |
| */ |
| struct hi3660_chan_info { |
| unsigned int dst_irq; |
| unsigned int ack_irq; |
| }; |
| |
| /** |
| * struct hi3660_mbox - Hi3660 mailbox controller data |
| * @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 |
| * |
| * Mailbox controller includes 32 channels and can allocate |
| * channel for message transferring. |
| */ |
| 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 ready to use */ |
| if (readl(base + MBOX_MODE_REG) & MBOX_STATE_READY) |
| 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; |
| } |
| |
| /* clear ack state, mailbox will get back to ready state */ |
| writel(BIT(mchan->ack_irq), base + MBOX_ICLR_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_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; |
| int ret; |
| |
| ret = hi3660_mbox_check_state(chan); |
| if (ret) |
| return ret; |
| |
| /* 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 const 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; |
| unsigned long ch; |
| int err; |
| |
| mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); |
| if (!mbox) |
| return -ENOMEM; |
| |
| mbox->base = devm_platform_ioremap_resource(pdev, 0); |
| 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 = devm_mbox_controller_register(dev, &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 struct platform_driver hi3660_mbox_driver = { |
| .probe = hi3660_mbox_probe, |
| .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>"); |