1*9952f691SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2f62092f6SLey Foon Tan /* 3f62092f6SLey Foon Tan * Copyright Altera Corporation (C) 2013-2014. All rights reserved 4f62092f6SLey Foon Tan */ 5f62092f6SLey Foon Tan 6f62092f6SLey Foon Tan #include <linux/device.h> 7f62092f6SLey Foon Tan #include <linux/interrupt.h> 8f62092f6SLey Foon Tan #include <linux/io.h> 9f62092f6SLey Foon Tan #include <linux/kernel.h> 10f62092f6SLey Foon Tan #include <linux/mailbox_controller.h> 11f62092f6SLey Foon Tan #include <linux/module.h> 12f62092f6SLey Foon Tan #include <linux/of.h> 13f62092f6SLey Foon Tan #include <linux/platform_device.h> 14f62092f6SLey Foon Tan 15f62092f6SLey Foon Tan #define DRIVER_NAME "altera-mailbox" 16f62092f6SLey Foon Tan 17f62092f6SLey Foon Tan #define MAILBOX_CMD_REG 0x00 18f62092f6SLey Foon Tan #define MAILBOX_PTR_REG 0x04 19f62092f6SLey Foon Tan #define MAILBOX_STS_REG 0x08 20f62092f6SLey Foon Tan #define MAILBOX_INTMASK_REG 0x0C 21f62092f6SLey Foon Tan 22f62092f6SLey Foon Tan #define INT_PENDING_MSK 0x1 23f62092f6SLey Foon Tan #define INT_SPACE_MSK 0x2 24f62092f6SLey Foon Tan 25f62092f6SLey Foon Tan #define STS_PENDING_MSK 0x1 26f62092f6SLey Foon Tan #define STS_FULL_MSK 0x2 27f62092f6SLey Foon Tan #define STS_FULL_OFT 0x1 28f62092f6SLey Foon Tan 29f62092f6SLey Foon Tan #define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) 30f62092f6SLey Foon Tan #define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) 31f62092f6SLey Foon Tan 32f62092f6SLey Foon Tan enum altera_mbox_msg { 33f62092f6SLey Foon Tan MBOX_CMD = 0, 34f62092f6SLey Foon Tan MBOX_PTR, 35f62092f6SLey Foon Tan }; 36f62092f6SLey Foon Tan 37f62092f6SLey Foon Tan #define MBOX_POLLING_MS 5 /* polling interval 5ms */ 38f62092f6SLey Foon Tan 39f62092f6SLey Foon Tan struct altera_mbox { 40f62092f6SLey Foon Tan bool is_sender; /* 1-sender, 0-receiver */ 41f62092f6SLey Foon Tan bool intr_mode; 42f62092f6SLey Foon Tan int irq; 43f62092f6SLey Foon Tan void __iomem *mbox_base; 44f62092f6SLey Foon Tan struct device *dev; 45f62092f6SLey Foon Tan struct mbox_controller controller; 46f62092f6SLey Foon Tan 47f62092f6SLey Foon Tan /* If the controller supports only RX polling mode */ 48f62092f6SLey Foon Tan struct timer_list rxpoll_timer; 49c6f15047SKees Cook struct mbox_chan *chan; 50f62092f6SLey Foon Tan }; 51f62092f6SLey Foon Tan 52f62092f6SLey Foon Tan static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan) 53f62092f6SLey Foon Tan { 54f62092f6SLey Foon Tan if (!chan || !chan->con_priv) 55f62092f6SLey Foon Tan return NULL; 56f62092f6SLey Foon Tan 57f62092f6SLey Foon Tan return (struct altera_mbox *)chan->con_priv; 58f62092f6SLey Foon Tan } 59f62092f6SLey Foon Tan 60f62092f6SLey Foon Tan static inline int altera_mbox_full(struct altera_mbox *mbox) 61f62092f6SLey Foon Tan { 62f62092f6SLey Foon Tan u32 status; 63f62092f6SLey Foon Tan 64f62092f6SLey Foon Tan status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); 65f62092f6SLey Foon Tan return MBOX_FULL(status); 66f62092f6SLey Foon Tan } 67f62092f6SLey Foon Tan 68f62092f6SLey Foon Tan static inline int altera_mbox_pending(struct altera_mbox *mbox) 69f62092f6SLey Foon Tan { 70f62092f6SLey Foon Tan u32 status; 71f62092f6SLey Foon Tan 72f62092f6SLey Foon Tan status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); 73f62092f6SLey Foon Tan return MBOX_PENDING(status); 74f62092f6SLey Foon Tan } 75f62092f6SLey Foon Tan 76f62092f6SLey Foon Tan static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) 77f62092f6SLey Foon Tan { 78f62092f6SLey Foon Tan u32 mask; 79f62092f6SLey Foon Tan 80f62092f6SLey Foon Tan mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); 81f62092f6SLey Foon Tan if (enable) 82f62092f6SLey Foon Tan mask |= INT_PENDING_MSK; 83f62092f6SLey Foon Tan else 84f62092f6SLey Foon Tan mask &= ~INT_PENDING_MSK; 85f62092f6SLey Foon Tan writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); 86f62092f6SLey Foon Tan } 87f62092f6SLey Foon Tan 88f62092f6SLey Foon Tan static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) 89f62092f6SLey Foon Tan { 90f62092f6SLey Foon Tan u32 mask; 91f62092f6SLey Foon Tan 92f62092f6SLey Foon Tan mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); 93f62092f6SLey Foon Tan if (enable) 94f62092f6SLey Foon Tan mask |= INT_SPACE_MSK; 95f62092f6SLey Foon Tan else 96f62092f6SLey Foon Tan mask &= ~INT_SPACE_MSK; 97f62092f6SLey Foon Tan writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); 98f62092f6SLey Foon Tan } 99f62092f6SLey Foon Tan 100f62092f6SLey Foon Tan static bool altera_mbox_is_sender(struct altera_mbox *mbox) 101f62092f6SLey Foon Tan { 102f62092f6SLey Foon Tan u32 reg; 103f62092f6SLey Foon Tan /* Write a magic number to PTR register and read back this register. 104f62092f6SLey Foon Tan * This register is read-write if it is a sender. 105f62092f6SLey Foon Tan */ 106f62092f6SLey Foon Tan #define MBOX_MAGIC 0xA5A5AA55 107f62092f6SLey Foon Tan writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); 108f62092f6SLey Foon Tan reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); 109f62092f6SLey Foon Tan if (reg == MBOX_MAGIC) { 110f62092f6SLey Foon Tan /* Clear to 0 */ 111f62092f6SLey Foon Tan writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG); 112f62092f6SLey Foon Tan return true; 113f62092f6SLey Foon Tan } 114f62092f6SLey Foon Tan return false; 115f62092f6SLey Foon Tan } 116f62092f6SLey Foon Tan 117f62092f6SLey Foon Tan static void altera_mbox_rx_data(struct mbox_chan *chan) 118f62092f6SLey Foon Tan { 119f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 120f62092f6SLey Foon Tan u32 data[2]; 121f62092f6SLey Foon Tan 122f62092f6SLey Foon Tan if (altera_mbox_pending(mbox)) { 123f62092f6SLey Foon Tan data[MBOX_PTR] = 124f62092f6SLey Foon Tan readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); 125f62092f6SLey Foon Tan data[MBOX_CMD] = 126f62092f6SLey Foon Tan readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG); 127f62092f6SLey Foon Tan mbox_chan_received_data(chan, (void *)data); 128f62092f6SLey Foon Tan } 129f62092f6SLey Foon Tan } 130f62092f6SLey Foon Tan 131c6f15047SKees Cook static void altera_mbox_poll_rx(struct timer_list *t) 132f62092f6SLey Foon Tan { 133c6f15047SKees Cook struct altera_mbox *mbox = from_timer(mbox, t, rxpoll_timer); 134f62092f6SLey Foon Tan 135c6f15047SKees Cook altera_mbox_rx_data(mbox->chan); 136f62092f6SLey Foon Tan 137f62092f6SLey Foon Tan mod_timer(&mbox->rxpoll_timer, 138f62092f6SLey Foon Tan jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); 139f62092f6SLey Foon Tan } 140f62092f6SLey Foon Tan 141f62092f6SLey Foon Tan static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) 142f62092f6SLey Foon Tan { 143f62092f6SLey Foon Tan struct mbox_chan *chan = (struct mbox_chan *)p; 144f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 145f62092f6SLey Foon Tan 146f62092f6SLey Foon Tan altera_mbox_tx_intmask(mbox, false); 147f62092f6SLey Foon Tan mbox_chan_txdone(chan, 0); 148f62092f6SLey Foon Tan 149f62092f6SLey Foon Tan return IRQ_HANDLED; 150f62092f6SLey Foon Tan } 151f62092f6SLey Foon Tan 152f62092f6SLey Foon Tan static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) 153f62092f6SLey Foon Tan { 154f62092f6SLey Foon Tan struct mbox_chan *chan = (struct mbox_chan *)p; 155f62092f6SLey Foon Tan 156f62092f6SLey Foon Tan altera_mbox_rx_data(chan); 157f62092f6SLey Foon Tan return IRQ_HANDLED; 158f62092f6SLey Foon Tan } 159f62092f6SLey Foon Tan 160f62092f6SLey Foon Tan static int altera_mbox_startup_sender(struct mbox_chan *chan) 161f62092f6SLey Foon Tan { 162f62092f6SLey Foon Tan int ret; 163f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 164f62092f6SLey Foon Tan 165f62092f6SLey Foon Tan if (mbox->intr_mode) { 166f62092f6SLey Foon Tan ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, 167f62092f6SLey Foon Tan DRIVER_NAME, chan); 168f62092f6SLey Foon Tan if (unlikely(ret)) { 169f62092f6SLey Foon Tan dev_err(mbox->dev, 170f62092f6SLey Foon Tan "failed to register mailbox interrupt:%d\n", 171f62092f6SLey Foon Tan ret); 172f62092f6SLey Foon Tan return ret; 173f62092f6SLey Foon Tan } 174f62092f6SLey Foon Tan } 175f62092f6SLey Foon Tan 176f62092f6SLey Foon Tan return 0; 177f62092f6SLey Foon Tan } 178f62092f6SLey Foon Tan 179f62092f6SLey Foon Tan static int altera_mbox_startup_receiver(struct mbox_chan *chan) 180f62092f6SLey Foon Tan { 181f62092f6SLey Foon Tan int ret; 182f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 183f62092f6SLey Foon Tan 184f62092f6SLey Foon Tan if (mbox->intr_mode) { 185f62092f6SLey Foon Tan ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, 186f62092f6SLey Foon Tan DRIVER_NAME, chan); 187f62092f6SLey Foon Tan if (unlikely(ret)) { 188f62092f6SLey Foon Tan mbox->intr_mode = false; 189f62092f6SLey Foon Tan goto polling; /* use polling if failed */ 190f62092f6SLey Foon Tan } 191f62092f6SLey Foon Tan 192f62092f6SLey Foon Tan altera_mbox_rx_intmask(mbox, true); 193f62092f6SLey Foon Tan return 0; 194f62092f6SLey Foon Tan } 195f62092f6SLey Foon Tan 196f62092f6SLey Foon Tan polling: 197f62092f6SLey Foon Tan /* Setup polling timer */ 198c6f15047SKees Cook mbox->chan = chan; 199c6f15047SKees Cook timer_setup(&mbox->rxpoll_timer, altera_mbox_poll_rx, 0); 200f62092f6SLey Foon Tan mod_timer(&mbox->rxpoll_timer, 201f62092f6SLey Foon Tan jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); 202f62092f6SLey Foon Tan 203f62092f6SLey Foon Tan return 0; 204f62092f6SLey Foon Tan } 205f62092f6SLey Foon Tan 206f62092f6SLey Foon Tan static int altera_mbox_send_data(struct mbox_chan *chan, void *data) 207f62092f6SLey Foon Tan { 208f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 209f62092f6SLey Foon Tan u32 *udata = (u32 *)data; 210f62092f6SLey Foon Tan 211f62092f6SLey Foon Tan if (!mbox || !data) 212f62092f6SLey Foon Tan return -EINVAL; 213f62092f6SLey Foon Tan if (!mbox->is_sender) { 214f62092f6SLey Foon Tan dev_warn(mbox->dev, 215f62092f6SLey Foon Tan "failed to send. This is receiver mailbox.\n"); 216f62092f6SLey Foon Tan return -EINVAL; 217f62092f6SLey Foon Tan } 218f62092f6SLey Foon Tan 219f62092f6SLey Foon Tan if (altera_mbox_full(mbox)) 220f62092f6SLey Foon Tan return -EBUSY; 221f62092f6SLey Foon Tan 222f62092f6SLey Foon Tan /* Enable interrupt before send */ 223f62092f6SLey Foon Tan if (mbox->intr_mode) 224f62092f6SLey Foon Tan altera_mbox_tx_intmask(mbox, true); 225f62092f6SLey Foon Tan 226f62092f6SLey Foon Tan /* Pointer register must write before command register */ 227f62092f6SLey Foon Tan writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); 228f62092f6SLey Foon Tan writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); 229f62092f6SLey Foon Tan 230f62092f6SLey Foon Tan return 0; 231f62092f6SLey Foon Tan } 232f62092f6SLey Foon Tan 233f62092f6SLey Foon Tan static bool altera_mbox_last_tx_done(struct mbox_chan *chan) 234f62092f6SLey Foon Tan { 235f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 236f62092f6SLey Foon Tan 237f62092f6SLey Foon Tan /* Return false if mailbox is full */ 238f62092f6SLey Foon Tan return altera_mbox_full(mbox) ? false : true; 239f62092f6SLey Foon Tan } 240f62092f6SLey Foon Tan 241f62092f6SLey Foon Tan static bool altera_mbox_peek_data(struct mbox_chan *chan) 242f62092f6SLey Foon Tan { 243f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 244f62092f6SLey Foon Tan 245f62092f6SLey Foon Tan return altera_mbox_pending(mbox) ? true : false; 246f62092f6SLey Foon Tan } 247f62092f6SLey Foon Tan 248f62092f6SLey Foon Tan static int altera_mbox_startup(struct mbox_chan *chan) 249f62092f6SLey Foon Tan { 250f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 251f62092f6SLey Foon Tan int ret = 0; 252f62092f6SLey Foon Tan 253f62092f6SLey Foon Tan if (!mbox) 254f62092f6SLey Foon Tan return -EINVAL; 255f62092f6SLey Foon Tan 256f62092f6SLey Foon Tan if (mbox->is_sender) 257f62092f6SLey Foon Tan ret = altera_mbox_startup_sender(chan); 258f62092f6SLey Foon Tan else 259f62092f6SLey Foon Tan ret = altera_mbox_startup_receiver(chan); 260f62092f6SLey Foon Tan 261f62092f6SLey Foon Tan return ret; 262f62092f6SLey Foon Tan } 263f62092f6SLey Foon Tan 264f62092f6SLey Foon Tan static void altera_mbox_shutdown(struct mbox_chan *chan) 265f62092f6SLey Foon Tan { 266f62092f6SLey Foon Tan struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); 267f62092f6SLey Foon Tan 268f62092f6SLey Foon Tan if (mbox->intr_mode) { 269f62092f6SLey Foon Tan /* Unmask all interrupt masks */ 270f62092f6SLey Foon Tan writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); 271f62092f6SLey Foon Tan free_irq(mbox->irq, chan); 272f62092f6SLey Foon Tan } else if (!mbox->is_sender) { 273f62092f6SLey Foon Tan del_timer_sync(&mbox->rxpoll_timer); 274f62092f6SLey Foon Tan } 275f62092f6SLey Foon Tan } 276f62092f6SLey Foon Tan 27705ae7975SAndrew Bresticker static const struct mbox_chan_ops altera_mbox_ops = { 278f62092f6SLey Foon Tan .send_data = altera_mbox_send_data, 279f62092f6SLey Foon Tan .startup = altera_mbox_startup, 280f62092f6SLey Foon Tan .shutdown = altera_mbox_shutdown, 281f62092f6SLey Foon Tan .last_tx_done = altera_mbox_last_tx_done, 282f62092f6SLey Foon Tan .peek_data = altera_mbox_peek_data, 283f62092f6SLey Foon Tan }; 284f62092f6SLey Foon Tan 285f62092f6SLey Foon Tan static int altera_mbox_probe(struct platform_device *pdev) 286f62092f6SLey Foon Tan { 287f62092f6SLey Foon Tan struct altera_mbox *mbox; 288f62092f6SLey Foon Tan struct resource *regs; 289f62092f6SLey Foon Tan struct mbox_chan *chans; 290f62092f6SLey Foon Tan int ret; 291f62092f6SLey Foon Tan 292f62092f6SLey Foon Tan mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), 293f62092f6SLey Foon Tan GFP_KERNEL); 294f62092f6SLey Foon Tan if (!mbox) 295f62092f6SLey Foon Tan return -ENOMEM; 296f62092f6SLey Foon Tan 297f62092f6SLey Foon Tan /* Allocated one channel */ 298f62092f6SLey Foon Tan chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); 299f62092f6SLey Foon Tan if (!chans) 300f62092f6SLey Foon Tan return -ENOMEM; 301f62092f6SLey Foon Tan 302f62092f6SLey Foon Tan regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); 303f62092f6SLey Foon Tan 304f62092f6SLey Foon Tan mbox->mbox_base = devm_ioremap_resource(&pdev->dev, regs); 305f62092f6SLey Foon Tan if (IS_ERR(mbox->mbox_base)) 306f62092f6SLey Foon Tan return PTR_ERR(mbox->mbox_base); 307f62092f6SLey Foon Tan 308f62092f6SLey Foon Tan /* Check is it a sender or receiver? */ 309f62092f6SLey Foon Tan mbox->is_sender = altera_mbox_is_sender(mbox); 310f62092f6SLey Foon Tan 311f62092f6SLey Foon Tan mbox->irq = platform_get_irq(pdev, 0); 312f62092f6SLey Foon Tan if (mbox->irq >= 0) 313f62092f6SLey Foon Tan mbox->intr_mode = true; 314f62092f6SLey Foon Tan 315f62092f6SLey Foon Tan mbox->dev = &pdev->dev; 316f62092f6SLey Foon Tan 317f62092f6SLey Foon Tan /* Hardware supports only one channel. */ 318f62092f6SLey Foon Tan chans[0].con_priv = mbox; 319f62092f6SLey Foon Tan mbox->controller.dev = mbox->dev; 320f62092f6SLey Foon Tan mbox->controller.num_chans = 1; 321f62092f6SLey Foon Tan mbox->controller.chans = chans; 322f62092f6SLey Foon Tan mbox->controller.ops = &altera_mbox_ops; 323f62092f6SLey Foon Tan 324f62092f6SLey Foon Tan if (mbox->is_sender) { 325f62092f6SLey Foon Tan if (mbox->intr_mode) { 326f62092f6SLey Foon Tan mbox->controller.txdone_irq = true; 327f62092f6SLey Foon Tan } else { 328f62092f6SLey Foon Tan mbox->controller.txdone_poll = true; 329f62092f6SLey Foon Tan mbox->controller.txpoll_period = MBOX_POLLING_MS; 330f62092f6SLey Foon Tan } 331f62092f6SLey Foon Tan } 332f62092f6SLey Foon Tan 33387f63f57SThierry Reding ret = devm_mbox_controller_register(&pdev->dev, &mbox->controller); 334f62092f6SLey Foon Tan if (ret) { 335f62092f6SLey Foon Tan dev_err(&pdev->dev, "Register mailbox failed\n"); 336f62092f6SLey Foon Tan goto err; 337f62092f6SLey Foon Tan } 338f62092f6SLey Foon Tan 339f62092f6SLey Foon Tan platform_set_drvdata(pdev, mbox); 340f62092f6SLey Foon Tan err: 341f62092f6SLey Foon Tan return ret; 342f62092f6SLey Foon Tan } 343f62092f6SLey Foon Tan 344f62092f6SLey Foon Tan static const struct of_device_id altera_mbox_match[] = { 345f62092f6SLey Foon Tan { .compatible = "altr,mailbox-1.0" }, 346f62092f6SLey Foon Tan { /* Sentinel */ } 347f62092f6SLey Foon Tan }; 348f62092f6SLey Foon Tan 349f62092f6SLey Foon Tan MODULE_DEVICE_TABLE(of, altera_mbox_match); 350f62092f6SLey Foon Tan 351f62092f6SLey Foon Tan static struct platform_driver altera_mbox_driver = { 352f62092f6SLey Foon Tan .probe = altera_mbox_probe, 353f62092f6SLey Foon Tan .driver = { 354f62092f6SLey Foon Tan .name = DRIVER_NAME, 355f62092f6SLey Foon Tan .of_match_table = altera_mbox_match, 356f62092f6SLey Foon Tan }, 357f62092f6SLey Foon Tan }; 358f62092f6SLey Foon Tan 359f62092f6SLey Foon Tan module_platform_driver(altera_mbox_driver); 360f62092f6SLey Foon Tan 361f62092f6SLey Foon Tan MODULE_LICENSE("GPL v2"); 362f62092f6SLey Foon Tan MODULE_DESCRIPTION("Altera mailbox specific functions"); 363f62092f6SLey Foon Tan MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); 364f62092f6SLey Foon Tan MODULE_ALIAS("platform:altera-mailbox"); 365