// SPDX-License-Identifier: GPL-2.0-only /* * Spreadtrum mailbox driver * * Copyright (c) 2020 Spreadtrum Communications Inc. */ #include #include #include #include #include #include #include #include #include #define SPRD_MBOX_ID 0x0 #define SPRD_MBOX_MSG_LOW 0x4 #define SPRD_MBOX_MSG_HIGH 0x8 #define SPRD_MBOX_TRIGGER 0xc #define SPRD_MBOX_FIFO_RST 0x10 #define SPRD_MBOX_FIFO_STS 0x14 #define SPRD_MBOX_IRQ_STS 0x18 #define SPRD_MBOX_IRQ_MSK 0x1c #define SPRD_MBOX_LOCK 0x20 #define SPRD_MBOX_FIFO_DEPTH 0x24 /* Bit and mask definition for inbox's SPRD_MBOX_FIFO_STS register */ #define SPRD_INBOX_FIFO_DELIVER_MASK GENMASK(23, 16) #define SPRD_INBOX_FIFO_OVERLOW_MASK GENMASK(15, 8) #define SPRD_INBOX_FIFO_DELIVER_SHIFT 16 #define SPRD_INBOX_FIFO_BUSY_MASK GENMASK(7, 0) /* Bit and mask definition for SPRD_MBOX_IRQ_STS register */ #define SPRD_MBOX_IRQ_CLR BIT(0) /* Bit and mask definition for outbox's SPRD_MBOX_FIFO_STS register */ #define SPRD_OUTBOX_FIFO_FULL BIT(2) #define SPRD_OUTBOX_FIFO_WR_SHIFT 16 #define SPRD_OUTBOX_FIFO_RD_SHIFT 24 #define SPRD_OUTBOX_FIFO_POS_MASK GENMASK(7, 0) /* Bit and mask definition for inbox's SPRD_MBOX_IRQ_MSK register */ #define SPRD_INBOX_FIFO_BLOCK_IRQ BIT(0) #define SPRD_INBOX_FIFO_OVERFLOW_IRQ BIT(1) #define SPRD_INBOX_FIFO_DELIVER_IRQ BIT(2) #define SPRD_INBOX_FIFO_IRQ_MASK GENMASK(2, 0) /* Bit and mask definition for outbox's SPRD_MBOX_IRQ_MSK register */ #define SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ BIT(0) #define SPRD_OUTBOX_FIFO_IRQ_MASK GENMASK(4, 0) #define SPRD_OUTBOX_BASE_SPAN 0x1000 #define SPRD_MBOX_CHAN_MAX 8 #define SPRD_SUPP_INBOX_ID_SC9863A 7 struct sprd_mbox_priv { struct mbox_controller mbox; struct device *dev; void __iomem *inbox_base; void __iomem *outbox_base; /* Base register address for supplementary outbox */ void __iomem *supp_base; struct clk *clk; u32 outbox_fifo_depth; struct mutex lock; u32 refcnt; struct mbox_chan chan[SPRD_MBOX_CHAN_MAX]; }; static struct sprd_mbox_priv *to_sprd_mbox_priv(struct mbox_controller *mbox) { return container_of(mbox, struct sprd_mbox_priv, mbox); } static u32 sprd_mbox_get_fifo_len(struct sprd_mbox_priv *priv, u32 fifo_sts) { u32 wr_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_WR_SHIFT) & SPRD_OUTBOX_FIFO_POS_MASK; u32 rd_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_RD_SHIFT) & SPRD_OUTBOX_FIFO_POS_MASK; u32 fifo_len; /* * If the read pointer is equal with write pointer, which means the fifo * is full or empty. */ if (wr_pos == rd_pos) { if (fifo_sts & SPRD_OUTBOX_FIFO_FULL) fifo_len = priv->outbox_fifo_depth; else fifo_len = 0; } else if (wr_pos > rd_pos) { fifo_len = wr_pos - rd_pos; } else { fifo_len = priv->outbox_fifo_depth - rd_pos + wr_pos; } return fifo_len; } static irqreturn_t do_outbox_isr(void __iomem *base, struct sprd_mbox_priv *priv) { struct mbox_chan *chan; u32 fifo_sts, fifo_len, msg[2]; int i, id; fifo_sts = readl(base + SPRD_MBOX_FIFO_STS); fifo_len = sprd_mbox_get_fifo_len(priv, fifo_sts); if (!fifo_len) { dev_warn_ratelimited(priv->dev, "spurious outbox interrupt\n"); return IRQ_NONE; } for (i = 0; i < fifo_len; i++) { msg[0] = readl(base + SPRD_MBOX_MSG_LOW); msg[1] = readl(base + SPRD_MBOX_MSG_HIGH); id = readl(base + SPRD_MBOX_ID); chan = &priv->chan[id]; if (chan->cl) mbox_chan_received_data(chan, (void *)msg); else dev_warn_ratelimited(priv->dev, "message's been dropped at ch[%d]\n", id); /* Trigger to update outbox FIFO pointer */ writel(0x1, base + SPRD_MBOX_TRIGGER); } /* Clear irq status after reading all message. */ writel(SPRD_MBOX_IRQ_CLR, base + SPRD_MBOX_IRQ_STS); return IRQ_HANDLED; } static irqreturn_t sprd_mbox_outbox_isr(int irq, void *data) { struct sprd_mbox_priv *priv = data; return do_outbox_isr(priv->outbox_base, priv); } static irqreturn_t sprd_mbox_supp_isr(int irq, void *data) { struct sprd_mbox_priv *priv = data; return do_outbox_isr(priv->supp_base, priv); } static irqreturn_t sprd_mbox_inbox_isr(int irq, void *data) { struct sprd_mbox_priv *priv = data; struct mbox_chan *chan; u32 fifo_sts, send_sts, busy, id; fifo_sts = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS); /* Get the inbox data delivery status */ send_sts = (fifo_sts & SPRD_INBOX_FIFO_DELIVER_MASK) >> SPRD_INBOX_FIFO_DELIVER_SHIFT; if (!send_sts) { dev_warn_ratelimited(priv->dev, "spurious inbox interrupt\n"); return IRQ_NONE; } while (send_sts) { id = __ffs(send_sts); send_sts &= (send_sts - 1); chan = &priv->chan[id]; /* * Check if the message was fetched by remote target, if yes, * that means the transmission has been completed. */ busy = fifo_sts & SPRD_INBOX_FIFO_BUSY_MASK; if (!(busy & BIT(id))) mbox_chan_txdone(chan, 0); } /* Clear FIFO delivery and overflow status */ writel(fifo_sts & (SPRD_INBOX_FIFO_DELIVER_MASK | SPRD_INBOX_FIFO_OVERLOW_MASK), priv->inbox_base + SPRD_MBOX_FIFO_RST); /* Clear irq status */ writel(SPRD_MBOX_IRQ_CLR, priv->inbox_base + SPRD_MBOX_IRQ_STS); return IRQ_HANDLED; } static int sprd_mbox_send_data(struct mbox_chan *chan, void *msg) { struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); unsigned long id = (unsigned long)chan->con_priv; u32 *data = msg; /* Write data into inbox FIFO, and only support 8 bytes every time */ writel(data[0], priv->inbox_base + SPRD_MBOX_MSG_LOW); writel(data[1], priv->inbox_base + SPRD_MBOX_MSG_HIGH); /* Set target core id */ writel(id, priv->inbox_base + SPRD_MBOX_ID); /* Trigger remote request */ writel(0x1, priv->inbox_base + SPRD_MBOX_TRIGGER); return 0; } static int sprd_mbox_flush(struct mbox_chan *chan, unsigned long timeout) { struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); unsigned long id = (unsigned long)chan->con_priv; u32 busy; timeout = jiffies + msecs_to_jiffies(timeout); while (time_before(jiffies, timeout)) { busy = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS) & SPRD_INBOX_FIFO_BUSY_MASK; if (!(busy & BIT(id))) { mbox_chan_txdone(chan, 0); return 0; } udelay(1); } return -ETIME; } static int sprd_mbox_startup(struct mbox_chan *chan) { struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); u32 val; mutex_lock(&priv->lock); if (priv->refcnt++ == 0) { /* Select outbox FIFO mode and reset the outbox FIFO status */ writel(0x0, priv->outbox_base + SPRD_MBOX_FIFO_RST); /* Enable inbox FIFO overflow and delivery interrupt */ val = readl(priv->inbox_base + SPRD_MBOX_IRQ_MSK); val &= ~(SPRD_INBOX_FIFO_OVERFLOW_IRQ | SPRD_INBOX_FIFO_DELIVER_IRQ); writel(val, priv->inbox_base + SPRD_MBOX_IRQ_MSK); /* Enable outbox FIFO not empty interrupt */ val = readl(priv->outbox_base + SPRD_MBOX_IRQ_MSK); val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ; writel(val, priv->outbox_base + SPRD_MBOX_IRQ_MSK); /* Enable supplementary outbox as the fundamental one */ if (priv->supp_base) { writel(0x0, priv->supp_base + SPRD_MBOX_FIFO_RST); val = readl(priv->supp_base + SPRD_MBOX_IRQ_MSK); val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ; writel(val, priv->supp_base + SPRD_MBOX_IRQ_MSK); } } mutex_unlock(&priv->lock); return 0; } static void sprd_mbox_shutdown(struct mbox_chan *chan) { struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox); mutex_lock(&priv->lock); if (--priv->refcnt == 0) { /* Disable inbox & outbox interrupt */ writel(SPRD_INBOX_FIFO_IRQ_MASK, priv->inbox_base + SPRD_MBOX_IRQ_MSK); writel(SPRD_OUTBOX_FIFO_IRQ_MASK, priv->outbox_base + SPRD_MBOX_IRQ_MSK); if (priv->supp_base) writel(SPRD_OUTBOX_FIFO_IRQ_MASK, priv->supp_base + SPRD_MBOX_IRQ_MSK); } mutex_unlock(&priv->lock); } static const struct mbox_chan_ops sprd_mbox_ops = { .send_data = sprd_mbox_send_data, .flush = sprd_mbox_flush, .startup = sprd_mbox_startup, .shutdown = sprd_mbox_shutdown, }; static void sprd_mbox_disable(void *data) { struct sprd_mbox_priv *priv = data; clk_disable_unprepare(priv->clk); } static int sprd_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct sprd_mbox_priv *priv; int ret, inbox_irq, outbox_irq, supp_irq; unsigned long id, supp; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; mutex_init(&priv->lock); /* * Unisoc mailbox uses an inbox to send messages to the target * core, and uses (an) outbox(es) to receive messages from other * cores. * * Thus in general the mailbox controller supplies 2 different * register addresses and IRQ numbers for inbox and outbox. * * If necessary, a supplementary inbox could be enabled optionally * with an independent FIFO and an extra interrupt. */ priv->inbox_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->inbox_base)) return PTR_ERR(priv->inbox_base); priv->outbox_base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(priv->outbox_base)) return PTR_ERR(priv->outbox_base); priv->clk = devm_clk_get(dev, "enable"); if (IS_ERR(priv->clk)) { dev_err(dev, "failed to get mailbox clock\n"); return PTR_ERR(priv->clk); } ret = clk_prepare_enable(priv->clk); if (ret) return ret; ret = devm_add_action_or_reset(dev, sprd_mbox_disable, priv); if (ret) { dev_err(dev, "failed to add mailbox disable action\n"); return ret; } inbox_irq = platform_get_irq_byname(pdev, "inbox"); if (inbox_irq < 0) return inbox_irq; ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); if (ret) { dev_err(dev, "failed to request inbox IRQ: %d\n", ret); return ret; } outbox_irq = platform_get_irq_byname(pdev, "outbox"); if (outbox_irq < 0) return outbox_irq; ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); if (ret) { dev_err(dev, "failed to request outbox IRQ: %d\n", ret); return ret; } /* Supplementary outbox IRQ is optional */ supp_irq = platform_get_irq_byname(pdev, "supp-outbox"); if (supp_irq > 0) { ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); if (ret) { dev_err(dev, "failed to request outbox IRQ: %d\n", ret); return ret; } supp = (unsigned long) of_device_get_match_data(dev); if (!supp) { dev_err(dev, "no supplementary outbox specified\n"); return -ENODEV; } priv->supp_base = priv->outbox_base + (SPRD_OUTBOX_BASE_SPAN * supp); } /* Get the default outbox FIFO depth */ priv->outbox_fifo_depth = readl(priv->outbox_base + SPRD_MBOX_FIFO_DEPTH) + 1; priv->mbox.dev = dev; priv->mbox.chans = &priv->chan[0]; priv->mbox.num_chans = SPRD_MBOX_CHAN_MAX; priv->mbox.ops = &sprd_mbox_ops; priv->mbox.txdone_irq = true; for (id = 0; id < SPRD_MBOX_CHAN_MAX; id++) priv->chan[id].con_priv = (void *)id; ret = devm_mbox_controller_register(dev, &priv->mbox); if (ret) { dev_err(dev, "failed to register mailbox: %d\n", ret); return ret; } return 0; } static const struct of_device_id sprd_mbox_of_match[] = { { .compatible = "sprd,sc9860-mailbox" }, { .compatible = "sprd,sc9863a-mailbox", .data = (void *)SPRD_SUPP_INBOX_ID_SC9863A }, { }, }; MODULE_DEVICE_TABLE(of, sprd_mbox_of_match); static struct platform_driver sprd_mbox_driver = { .driver = { .name = "sprd-mailbox", .of_match_table = sprd_mbox_of_match, }, .probe = sprd_mbox_probe, }; module_platform_driver(sprd_mbox_driver); MODULE_AUTHOR("Baolin Wang "); MODULE_DESCRIPTION("Spreadtrum mailbox driver"); MODULE_LICENSE("GPL v2");