// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020 Axis Communications AB */ #include #include #include #include #include #include #include #include #include #include #define MAILBOX_MCUCTLR 0x00 #define MAILBOX_INTGR0 0x08 #define MAILBOX_INTCR0 0x0c #define MAILBOX_INTMR0 0x10 #define MAILBOX_INTSR0 0x14 #define MAILBOX_INTMSR0 0x18 #define MAILBOX_INTGR1 0x1C #define MAILBOX_INTCR1 0x20 #define MAILBOX_INTMR1 0x24 #define MAILBOX_INTSR1 0x28 #define MAILBOX_INTMSR1 0x2C #define MAILBOX_IS_VERSION 0x50 #define MAILBOX_ISSR(n) (0x80 + (n) * 4) /* * We have 32 interrupts and thus 32 channels. We also have 64 general-purpose * ISSR registers, and we use these to send 32-bit values between the hosts for * each of the channels. The first 32 ISSR registers are used for data sent * from CPU1 to CPU0 and the remaining are used in the other direction. */ #define MAILBOX_NUM_CHANS 32 struct artpec8_mbox { void __iomem *base; unsigned int cpu; struct mutex mutex; int irq_low; int irq_high; struct mbox_controller controller; struct mbox_chan chans[MAILBOX_NUM_CHANS]; const struct artpec_endpoint_info *endpoint; u32 msi; }; static irqreturn_t artpec8_mbox_irq(int irq, void *p) { unsigned int cr, msr, issroffset = 0; struct artpec8_mbox *mbox = p; u32 clear, ints; if (mbox->cpu == 0) { msr = MAILBOX_INTMSR0; cr = MAILBOX_INTCR0; } else { msr = MAILBOX_INTMSR1; cr = MAILBOX_INTCR1; issroffset = 32; } ints = readl_relaxed(mbox->base + msr); if (!ints) return IRQ_NONE; clear = ints; while (ints) { unsigned int channum = __ffs(ints); struct mbox_chan *chan = &mbox->chans[channum]; u32 msg; ints &= ~BIT(channum); msg = readl_relaxed(mbox->base + MAILBOX_ISSR(channum + issroffset)); mbox_chan_received_data(chan, (void *)(unsigned long)msg); } writel_relaxed(clear, mbox->base + cr); return IRQ_HANDLED; } static int artpec8_mbox_send_data(struct mbox_chan *chan, void *data) { struct artpec8_mbox *mbox = chan->con_priv; unsigned int channum = chan - mbox->chans; unsigned int gr, issroffset = 0; u32 arg = (u32)(unsigned long)data; if (mbox->cpu == 0) { gr = MAILBOX_INTGR1; issroffset = 32; } else { gr = MAILBOX_INTGR0; } writel(arg, mbox->base + MAILBOX_ISSR(channum + issroffset)); writel(BIT(channum), mbox->base + gr); if (mbox->endpoint) writel(mbox->endpoint->msi_data + mbox->msi, mbox->endpoint->msi); return 0; } static bool artpec8_mbox_last_tx_done(struct mbox_chan *chan) { struct artpec8_mbox *mbox = chan->con_priv; unsigned int sr = mbox->cpu == 0 ? MAILBOX_INTSR1 : MAILBOX_INTSR0; unsigned int channum = chan - mbox->chans; return !(readl_relaxed(mbox->base + sr) & BIT(channum)); } static int artpec8_mbox_startup(struct mbox_chan *chan) { struct artpec8_mbox *mbox = chan->con_priv; unsigned int mr = mbox->cpu == 0 ? MAILBOX_INTMR0 : MAILBOX_INTMR1; unsigned int channum = chan - mbox->chans; u32 val; mutex_lock(&mbox->mutex); /* * The hardware manual claims that writing 0 has No Effect, but it also * doesn't provide any way to unmask interrupts, so I'm assuming that * that is a typo. */ val = readl_relaxed(mbox->base + mr); val &= ~BIT(channum); writel_relaxed(val, mbox->base + mr); mutex_unlock(&mbox->mutex); return 0; } static void artpec8_mbox_shutdown(struct mbox_chan *chan) { struct artpec8_mbox *mbox = chan->con_priv; unsigned int mr = mbox->cpu == 0 ? MAILBOX_INTMR0 : MAILBOX_INTMR1; unsigned int channum = chan - mbox->chans; u32 val; mutex_lock(&mbox->mutex); val = readl_relaxed(mbox->base + mr); val |= BIT(channum); writel_relaxed(val, mbox->base + mr); mutex_unlock(&mbox->mutex); } static const struct mbox_chan_ops artpec8_mbox_ops = { .send_data = artpec8_mbox_send_data, .startup = artpec8_mbox_startup, .shutdown = artpec8_mbox_shutdown, .last_tx_done = artpec8_mbox_last_tx_done, }; static int artpec8_mbox_reset(struct artpec8_mbox *mbox) { u32 val; int ret; /* We assume that CPU0 starts up first, so it should do the main reset. */ if (mbox->cpu != 0) { writel_relaxed(~0ul, mbox->base + MAILBOX_INTMR1); writel_relaxed(~0ul, mbox->base + MAILBOX_INTCR1); return 0; } /* * It's not entirely clear from the hardware manual, but this reset may * also clear the SEMAPHORE* registers used by the hwspinlock driver. * This will probably not be a problem in practice since both drivers * will presumably be used together by the same clients. */ writel_relaxed(1, mbox->base + MAILBOX_MCUCTLR); ret = readl_relaxed_poll_timeout(mbox->base + MAILBOX_MCUCTLR, val, !val, 0, 100); if (ret) return ret; writel_relaxed(~0ul, mbox->base + MAILBOX_INTMR0); writel_relaxed(~0ul, mbox->base + MAILBOX_INTCR0); return 0; } static int artpec8_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct artpec8_mbox *mbox; struct resource *res; const struct artpec_endpoint_info *endpoint; int ret; int i; mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); if (!mbox) return -ENOMEM; of_property_read_u32(dev->of_node, "cpu", &mbox->cpu); if (mbox->cpu > 1) return -EINVAL; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENXIO; mbox->base = devm_ioremap(dev, res->start, resource_size(res)); if (IS_ERR(mbox->base)) return PTR_ERR(mbox->base); mbox->irq_low = platform_get_irq_optional(pdev, 0); if (mbox->irq_low == -EPROBE_DEFER) return mbox->irq_low; mbox->irq_high = platform_get_irq_optional(pdev, 1); if (mbox->irq_high == -EPROBE_DEFER) return mbox->irq_high; /* Only enable MSI if present */ mbox->endpoint = NULL; endpoint = artpec_endpoint_get_info(&pdev->dev); if (endpoint) if (!of_property_read_u32(dev->of_node, "msi", &mbox->msi)) mbox->endpoint = endpoint; mutex_init(&mbox->mutex); mbox->controller.txdone_irq = false; mbox->controller.txdone_poll = true; mbox->controller.txpoll_period = 1; mbox->controller.dev = dev; mbox->controller.num_chans = ARRAY_SIZE(mbox->chans); mbox->controller.chans = mbox->chans; mbox->controller.ops = &artpec8_mbox_ops; for (i = 0; i < ARRAY_SIZE(mbox->chans); i++) mbox->chans[i].con_priv = mbox; ret = artpec8_mbox_reset(mbox); if (ret) return ret; if (mbox->irq_low > 0) { ret = devm_request_irq(dev, mbox->irq_low, artpec8_mbox_irq, 0, "mbox-low", mbox); if (ret) return ret; } if (mbox->irq_high > 0) { ret = devm_request_irq(dev, mbox->irq_high, artpec8_mbox_irq, 0, "mbox-high", mbox); if (ret) return ret; } platform_set_drvdata(pdev, mbox); return mbox_controller_register(&mbox->controller); } static int artpec8_mbox_remove(struct platform_device *pdev) { struct artpec8_mbox *mbox = platform_get_drvdata(pdev); mbox_controller_unregister(&mbox->controller); return 0; } static const struct of_device_id artpec8_mbox_match[] = { { .compatible = "samsung,artpec8-mailbox" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, artpec8_mbox_match); static struct platform_driver artpec8_mbox_driver = { .probe = artpec8_mbox_probe, .remove = artpec8_mbox_remove, .driver = { .name = "artpec8-mailbox", .of_match_table = artpec8_mbox_match, }, }; module_platform_driver(artpec8_mbox_driver); MODULE_LICENSE("GPL v2");