diff options
Diffstat (limited to 'drivers/mailbox')
-rw-r--r-- | drivers/mailbox/Kconfig | 29 | ||||
-rw-r--r-- | drivers/mailbox/Makefile | 6 | ||||
-rw-r--r-- | drivers/mailbox/ast2700-mailbox.c | 235 | ||||
-rw-r--r-- | drivers/mailbox/bcm74110-mailbox.c | 656 | ||||
-rw-r--r-- | drivers/mailbox/cix-mailbox.c | 645 | ||||
-rw-r--r-- | drivers/mailbox/mtk-cmdq-mailbox.c | 10 | ||||
-rw-r--r-- | drivers/mailbox/pcc.c | 102 | ||||
-rw-r--r-- | drivers/mailbox/qcom-ipcc.c | 3 |
8 files changed, 1675 insertions, 11 deletions
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 68eeed660a4a..02432d4a5ccd 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -36,6 +36,15 @@ config ARM_MHU_V3 that provides different means of transports: supported extensions will be discovered and possibly managed at probe-time. +config AST2700_MBOX + tristate "ASPEED AST2700 IPC driver" + depends on ARCH_ASPEED || COMPILE_TEST + help + Mailbox driver implementation for ASPEED AST27XX SoCs. This driver + can be used to send message between different processors in SoC. + The driver provides mailbox support for sending interrupts to the + clients. Say Y here if you want to build this driver. + config CV1800_MBOX tristate "cv1800 mailbox" depends on ARCH_SOPHGO || COMPILE_TEST @@ -340,4 +349,24 @@ config THEAD_TH1520_MBOX kernel is running, and E902 core used for power management among other things. +config CIX_MBOX + tristate "CIX Mailbox" + depends on ARCH_CIX || COMPILE_TEST + depends on OF + help + Mailbox implementation for CIX IPC system. The controller supports + 11 mailbox channels with different operating mode and every channel + is unidirectional. Say Y here if you want to use the CIX Mailbox + support. + +config BCM74110_MAILBOX + tristate "Brcmstb BCM74110 Mailbox" + depends on ARCH_BRCMSTB || COMPILE_TEST + default ARCH_BRCMSTB + help + Broadcom STB mailbox driver present starting with brcmstb bcm74110 + SoCs. The mailbox is a communication channel between the host + processor and coprocessor that handles various power management task + and more. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 13a3448b3271..98a68f838486 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_ARM_MHU_V2) += arm_mhuv2.o obj-$(CONFIG_ARM_MHU_V3) += arm_mhuv3.o +obj-$(CONFIG_AST2700_MBOX) += ast2700-mailbox.o + obj-$(CONFIG_CV1800_MBOX) += cv1800-mailbox.o obj-$(CONFIG_EXYNOS_MBOX) += exynos-mailbox.o @@ -72,3 +74,7 @@ obj-$(CONFIG_QCOM_CPUCP_MBOX) += qcom-cpucp-mbox.o obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o obj-$(CONFIG_THEAD_TH1520_MBOX) += mailbox-th1520.o + +obj-$(CONFIG_CIX_MBOX) += cix-mailbox.o + +obj-$(CONFIG_BCM74110_MAILBOX) += bcm74110-mailbox.o diff --git a/drivers/mailbox/ast2700-mailbox.c b/drivers/mailbox/ast2700-mailbox.c new file mode 100644 index 000000000000..83c6afe5411f --- /dev/null +++ b/drivers/mailbox/ast2700-mailbox.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright Aspeed Technology Inc. (C) 2025. All rights reserved + */ + +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Each bit in the register represents an IPC ID */ +#define IPCR_TX_TRIG 0x00 +#define IPCR_ENABLE 0x04 +#define IPCR_STATUS 0x08 +#define RX_IRQ(n) BIT(n) +#define RX_IRQ_MASK 0xf +#define IPCR_DATA 0x10 + +struct ast2700_mbox_data { + u8 num_chans; + u8 msg_size; +}; + +struct ast2700_mbox { + struct mbox_controller mbox; + u8 msg_size; + void __iomem *tx_regs; + void __iomem *rx_regs; + spinlock_t lock; +}; + +static inline int ch_num(struct mbox_chan *chan) +{ + return chan - chan->mbox->chans; +} + +static inline bool ast2700_mbox_tx_done(struct ast2700_mbox *mb, int idx) +{ + return !(readl(mb->tx_regs + IPCR_STATUS) & BIT(idx)); +} + +static irqreturn_t ast2700_mbox_irq(int irq, void *p) +{ + struct ast2700_mbox *mb = p; + void __iomem *data_reg; + int num_words = mb->msg_size / sizeof(u32); + u32 *word_data; + u32 status; + int n, i; + + /* Only examine channels that are currently enabled. */ + status = readl(mb->rx_regs + IPCR_ENABLE) & + readl(mb->rx_regs + IPCR_STATUS); + + if (!(status & RX_IRQ_MASK)) + return IRQ_NONE; + + for (n = 0; n < mb->mbox.num_chans; ++n) { + struct mbox_chan *chan = &mb->mbox.chans[n]; + + if (!(status & RX_IRQ(n))) + continue; + + data_reg = mb->rx_regs + IPCR_DATA + mb->msg_size * n; + word_data = chan->con_priv; + /* Read the message data */ + for (i = 0; i < num_words; i++) + word_data[i] = readl(data_reg + i * sizeof(u32)); + + mbox_chan_received_data(chan, chan->con_priv); + + /* The IRQ can be cleared only once the FIFO is empty. */ + writel(RX_IRQ(n), mb->rx_regs + IPCR_STATUS); + } + + return IRQ_HANDLED; +} + +static int ast2700_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); + int idx = ch_num(chan); + void __iomem *data_reg = mb->tx_regs + IPCR_DATA + mb->msg_size * idx; + u32 *word_data = data; + int num_words = mb->msg_size / sizeof(u32); + int i; + + if (!(readl(mb->tx_regs + IPCR_ENABLE) & BIT(idx))) { + dev_warn(mb->mbox.dev, "%s: Ch-%d not enabled yet\n", __func__, idx); + return -ENODEV; + } + + if (!(ast2700_mbox_tx_done(mb, idx))) { + dev_warn(mb->mbox.dev, "%s: Ch-%d last data has not finished\n", __func__, idx); + return -EBUSY; + } + + /* Write the message data */ + for (i = 0 ; i < num_words; i++) + writel(word_data[i], data_reg + i * sizeof(u32)); + + writel(BIT(idx), mb->tx_regs + IPCR_TX_TRIG); + dev_dbg(mb->mbox.dev, "%s: Ch-%d sent\n", __func__, idx); + + return 0; +} + +static int ast2700_mbox_startup(struct mbox_chan *chan) +{ + struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); + int idx = ch_num(chan); + void __iomem *reg = mb->rx_regs + IPCR_ENABLE; + unsigned long flags; + + spin_lock_irqsave(&mb->lock, flags); + writel(readl(reg) | BIT(idx), reg); + spin_unlock_irqrestore(&mb->lock, flags); + + return 0; +} + +static void ast2700_mbox_shutdown(struct mbox_chan *chan) +{ + struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); + int idx = ch_num(chan); + void __iomem *reg = mb->rx_regs + IPCR_ENABLE; + unsigned long flags; + + spin_lock_irqsave(&mb->lock, flags); + writel(readl(reg) & ~BIT(idx), reg); + spin_unlock_irqrestore(&mb->lock, flags); +} + +static bool ast2700_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct ast2700_mbox *mb = dev_get_drvdata(chan->mbox->dev); + int idx = ch_num(chan); + + return ast2700_mbox_tx_done(mb, idx); +} + +static const struct mbox_chan_ops ast2700_mbox_chan_ops = { + .send_data = ast2700_mbox_send_data, + .startup = ast2700_mbox_startup, + .shutdown = ast2700_mbox_shutdown, + .last_tx_done = ast2700_mbox_last_tx_done, +}; + +static int ast2700_mbox_probe(struct platform_device *pdev) +{ + struct ast2700_mbox *mb; + const struct ast2700_mbox_data *dev_data; + struct device *dev = &pdev->dev; + int irq, ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + dev_data = device_get_match_data(&pdev->dev); + + mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox.chans = devm_kcalloc(&pdev->dev, dev_data->num_chans, + sizeof(*mb->mbox.chans), GFP_KERNEL); + if (!mb->mbox.chans) + return -ENOMEM; + + /* con_priv of each channel is used to store the message received */ + for (int i = 0; i < dev_data->num_chans; i++) { + mb->mbox.chans[i].con_priv = devm_kcalloc(dev, dev_data->msg_size, + sizeof(u8), GFP_KERNEL); + if (!mb->mbox.chans[i].con_priv) + return -ENOMEM; + } + + platform_set_drvdata(pdev, mb); + + mb->tx_regs = devm_platform_ioremap_resource_byname(pdev, "tx"); + if (IS_ERR(mb->tx_regs)) + return PTR_ERR(mb->tx_regs); + + mb->rx_regs = devm_platform_ioremap_resource_byname(pdev, "rx"); + if (IS_ERR(mb->rx_regs)) + return PTR_ERR(mb->rx_regs); + + mb->msg_size = dev_data->msg_size; + mb->mbox.dev = dev; + mb->mbox.num_chans = dev_data->num_chans; + mb->mbox.ops = &ast2700_mbox_chan_ops; + mb->mbox.txdone_irq = false; + mb->mbox.txdone_poll = true; + mb->mbox.txpoll_period = 5; + spin_lock_init(&mb->lock); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, ast2700_mbox_irq, 0, dev_name(dev), mb); + if (ret) + return ret; + + return devm_mbox_controller_register(dev, &mb->mbox); +} + +static const struct ast2700_mbox_data ast2700_dev_data = { + .num_chans = 4, + .msg_size = 0x20, +}; + +static const struct of_device_id ast2700_mbox_of_match[] = { + { .compatible = "aspeed,ast2700-mailbox", .data = &ast2700_dev_data }, + {} +}; +MODULE_DEVICE_TABLE(of, ast2700_mbox_of_match); + +static struct platform_driver ast2700_mbox_driver = { + .driver = { + .name = "ast2700-mailbox", + .of_match_table = ast2700_mbox_of_match, + }, + .probe = ast2700_mbox_probe, +}; +module_platform_driver(ast2700_mbox_driver); + +MODULE_AUTHOR("Jammy Huang <jammy_huang@aspeedtech.com>"); +MODULE_DESCRIPTION("ASPEED AST2700 IPC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/bcm74110-mailbox.c b/drivers/mailbox/bcm74110-mailbox.c new file mode 100644 index 000000000000..2e7e86f3e6a4 --- /dev/null +++ b/drivers/mailbox/bcm74110-mailbox.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom BCM74110 Mailbox Driver + * + * Copyright (c) 2025 Broadcom + */ +#include <linux/list.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <linux/io-64-nonatomic-hi-lo.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/mailbox_controller.h> +#include <linux/bitfield.h> +#include <linux/slab.h> + +#define BCM_MBOX_BASE(sel) ((sel) * 0x40) +#define BCM_MBOX_IRQ_BASE(sel) (((sel) * 0x20) + 0x800) + +#define BCM_MBOX_CFGA 0x0 +#define BCM_MBOX_CFGB 0x4 +#define BCM_MBOX_CFGC 0x8 +#define BCM_MBOX_CFGD 0xc +#define BCM_MBOX_CTRL 0x10 +#define BCM_MBOX_CTRL_EN BIT(0) +#define BCM_MBOX_CTRL_CLR BIT(1) +#define BCM_MBOX_STATUS0 0x14 +#define BCM_MBOX_STATUS0_NOT_EMPTY BIT(28) +#define BCM_MBOX_STATUS0_FULL BIT(29) +#define BCM_MBOX_STATUS1 0x18 +#define BCM_MBOX_STATUS2 0x1c +#define BCM_MBOX_WDATA 0x20 +#define BCM_MBOX_RDATA 0x28 + +#define BCM_MBOX_IRQ_STATUS 0x0 +#define BCM_MBOX_IRQ_SET 0x4 +#define BCM_MBOX_IRQ_CLEAR 0x8 +#define BCM_MBOX_IRQ_MASK_STATUS 0xc +#define BCM_MBOX_IRQ_MASK_SET 0x10 +#define BCM_MBOX_IRQ_MASK_CLEAR 0x14 +#define BCM_MBOX_IRQ_TIMEOUT BIT(0) +#define BCM_MBOX_IRQ_NOT_EMPTY BIT(1) +#define BCM_MBOX_IRQ_FULL BIT(2) +#define BCM_MBOX_IRQ_LOW_WM BIT(3) +#define BCM_MBOX_IRQ_HIGH_WM BIT(4) + +#define BCM_LINK_CODE0 0xbe0 +#define BCM_LINK_CODE1 0xbe1 +#define BCM_LINK_CODE2 0xbe2 + +enum { + BCM_MSG_FUNC_LINK_START = 0, + BCM_MSG_FUNC_LINK_STOP, + BCM_MSG_FUNC_SHMEM_TX, + BCM_MSG_FUNC_SHMEM_RX, + BCM_MSG_FUNC_SHMEM_STOP, + BCM_MSG_FUNC_MAX, +}; + +enum { + BCM_MSG_SVC_INIT = 0, + BCM_MSG_SVC_PMC, + BCM_MSG_SVC_SCMI, + BCM_MSG_SVC_DPFE, + BCM_MSG_SVC_MAX, +}; + +struct bcm74110_mbox_msg { + struct list_head list_entry; +#define BCM_MSG_VERSION_MASK GENMASK(31, 29) +#define BCM_MSG_VERSION 0x1 +#define BCM_MSG_REQ_MASK BIT(28) +#define BCM_MSG_RPLY_MASK BIT(27) +#define BCM_MSG_SVC_MASK GENMASK(26, 24) +#define BCM_MSG_FUNC_MASK GENMASK(23, 16) +#define BCM_MSG_LENGTH_MASK GENMASK(15, 4) +#define BCM_MSG_SLOT_MASK GENMASK(3, 0) + +#define BCM_MSG_SET_FIELD(hdr, field, val) \ + do { \ + hdr &= ~BCM_MSG_##field##_MASK; \ + hdr |= FIELD_PREP(BCM_MSG_##field##_MASK, val); \ + } while (0) + +#define BCM_MSG_GET_FIELD(hdr, field) \ + FIELD_GET(BCM_MSG_##field##_MASK, hdr) + u32 msg; +}; + +struct bcm74110_mbox_chan { + struct bcm74110_mbox *mbox; + bool en; + int slot; + int type; +}; + +struct bcm74110_mbox { + struct platform_device *pdev; + void __iomem *base; + + int tx_chan; + int rx_chan; + int rx_irq; + struct list_head rx_svc_init_list; + spinlock_t rx_svc_list_lock; + + struct mbox_controller controller; + struct bcm74110_mbox_chan *mbox_chan; +}; + +#define BCM74110_OFFSET_IO_WRITEL_MACRO(name, offset_base) \ +static void bcm74110_##name##_writel(struct bcm74110_mbox *mbox,\ + u32 val, u32 off) \ +{ \ + writel_relaxed(val, mbox->base + offset_base + off); \ +} +BCM74110_OFFSET_IO_WRITEL_MACRO(tx, BCM_MBOX_BASE(mbox->tx_chan)); +BCM74110_OFFSET_IO_WRITEL_MACRO(irq, BCM_MBOX_IRQ_BASE(mbox->rx_chan)); + +#define BCM74110_OFFSET_IO_READL_MACRO(name, offset_base) \ +static u32 bcm74110_##name##_readl(struct bcm74110_mbox *mbox, \ + u32 off) \ +{ \ + return readl_relaxed(mbox->base + offset_base + off); \ +} +BCM74110_OFFSET_IO_READL_MACRO(tx, BCM_MBOX_BASE(mbox->tx_chan)); +BCM74110_OFFSET_IO_READL_MACRO(rx, BCM_MBOX_BASE(mbox->rx_chan)); +BCM74110_OFFSET_IO_READL_MACRO(irq, BCM_MBOX_IRQ_BASE(mbox->rx_chan)); + +static inline struct bcm74110_mbox *bcm74110_mbox_from_cntrl( + struct mbox_controller *cntrl) +{ + return container_of(cntrl, struct bcm74110_mbox, controller); +} + +static void bcm74110_rx_push_init_msg(struct bcm74110_mbox *mbox, u32 val) +{ + struct bcm74110_mbox_msg *msg; + + msg = kzalloc(sizeof(*msg), GFP_ATOMIC); + if (!msg) + return; + + INIT_LIST_HEAD(&msg->list_entry); + msg->msg = val; + + spin_lock(&mbox->rx_svc_list_lock); + list_add_tail(&msg->list_entry, &mbox->rx_svc_init_list); + spin_unlock(&mbox->rx_svc_list_lock); +} + +static void bcm74110_rx_process_msg(struct bcm74110_mbox *mbox) +{ + struct device *dev = &mbox->pdev->dev; + struct bcm74110_mbox_chan *chan_priv; + struct mbox_chan *chan; + u32 msg, status; + int type; + + do { + msg = bcm74110_rx_readl(mbox, BCM_MBOX_RDATA); + status = bcm74110_rx_readl(mbox, BCM_MBOX_STATUS0); + + dev_dbg(dev, "rx: [{req=%lu|rply=%lu|srv=%lu|fn=%lu|length=%lu|slot=%lu]\n", + BCM_MSG_GET_FIELD(msg, REQ), BCM_MSG_GET_FIELD(msg, RPLY), + BCM_MSG_GET_FIELD(msg, SVC), BCM_MSG_GET_FIELD(msg, FUNC), + BCM_MSG_GET_FIELD(msg, LENGTH), BCM_MSG_GET_FIELD(msg, SLOT)); + + type = BCM_MSG_GET_FIELD(msg, SVC); + switch (type) { + case BCM_MSG_SVC_INIT: + bcm74110_rx_push_init_msg(mbox, msg); + break; + case BCM_MSG_SVC_PMC: + case BCM_MSG_SVC_SCMI: + case BCM_MSG_SVC_DPFE: + chan = &mbox->controller.chans[type]; + chan_priv = chan->con_priv; + if (chan_priv->en) + mbox_chan_received_data(chan, NULL); + else + dev_warn(dev, "Channel not enabled\n"); + break; + default: + dev_warn(dev, "Unsupported msg received\n"); + } + } while (status & BCM_MBOX_STATUS0_NOT_EMPTY); +} + +static irqreturn_t bcm74110_mbox_isr(int irq, void *data) +{ + struct bcm74110_mbox *mbox = data; + u32 status; + + status = bcm74110_irq_readl(mbox, BCM_MBOX_IRQ_STATUS); + + bcm74110_irq_writel(mbox, 0xffffffff, BCM_MBOX_IRQ_CLEAR); + + if (status & BCM_MBOX_IRQ_NOT_EMPTY) + bcm74110_rx_process_msg(mbox); + else + dev_warn(&mbox->pdev->dev, "Spurious interrupt\n"); + + return IRQ_HANDLED; +} + +static void bcm74110_mbox_mask_and_clear(struct bcm74110_mbox *mbox) +{ + bcm74110_irq_writel(mbox, 0xffffffff, BCM_MBOX_IRQ_MASK_SET); + bcm74110_irq_writel(mbox, 0xffffffff, BCM_MBOX_IRQ_CLEAR); +} + +static int bcm74110_rx_pop_init_msg(struct bcm74110_mbox *mbox, u32 func_type, + u32 *val) +{ + struct bcm74110_mbox_msg *msg, *msg_tmp; + unsigned long flags; + bool found = false; + + spin_lock_irqsave(&mbox->rx_svc_list_lock, flags); + list_for_each_entry_safe(msg, msg_tmp, &mbox->rx_svc_init_list, + list_entry) { + if (BCM_MSG_GET_FIELD(msg->msg, FUNC) == func_type) { + list_del(&msg->list_entry); + found = true; + break; + } + } + spin_unlock_irqrestore(&mbox->rx_svc_list_lock, flags); + + if (!found) + return -EINVAL; + + *val = msg->msg; + kfree(msg); + + return 0; +} + +static void bcm74110_rx_flush_msg(struct bcm74110_mbox *mbox) +{ + struct bcm74110_mbox_msg *msg, *msg_tmp; + LIST_HEAD(list_temp); + unsigned long flags; + + spin_lock_irqsave(&mbox->rx_svc_list_lock, flags); + list_splice_init(&mbox->rx_svc_init_list, &list_temp); + spin_unlock_irqrestore(&mbox->rx_svc_list_lock, flags); + + list_for_each_entry_safe(msg, msg_tmp, &list_temp, list_entry) { + list_del(&msg->list_entry); + kfree(msg); + } +} + +#define BCM_DEQUEUE_TIMEOUT_MS 30 +static int bcm74110_rx_pop_init_msg_block(struct bcm74110_mbox *mbox, u32 func_type, + u32 *val) +{ + int ret, timeout = 0; + + do { + ret = bcm74110_rx_pop_init_msg(mbox, func_type, val); + + if (!ret) + return 0; + + /* TODO: Figure out what is a good sleep here. */ + usleep_range(1000, 2000); + timeout++; + } while (timeout < BCM_DEQUEUE_TIMEOUT_MS); + + dev_warn(&mbox->pdev->dev, "Timeout waiting for service init response\n"); + return -ETIMEDOUT; +} + +static int bcm74110_mbox_create_msg(int req, int rply, int svc, int func, + int length, int slot) +{ + u32 msg = 0; + + BCM_MSG_SET_FIELD(msg, REQ, req); + BCM_MSG_SET_FIELD(msg, RPLY, rply); + BCM_MSG_SET_FIELD(msg, SVC, svc); + BCM_MSG_SET_FIELD(msg, FUNC, func); + BCM_MSG_SET_FIELD(msg, LENGTH, length); + BCM_MSG_SET_FIELD(msg, SLOT, slot); + + return msg; +} + +static int bcm74110_mbox_tx_msg(struct bcm74110_mbox *mbox, u32 msg) +{ + int val; + + /* We can potentially poll with timeout here instead */ + val = bcm74110_tx_readl(mbox, BCM_MBOX_STATUS0); + if (val & BCM_MBOX_STATUS0_FULL) { + dev_err(&mbox->pdev->dev, "Mailbox full\n"); + return -EINVAL; + } + + dev_dbg(&mbox->pdev->dev, "tx: [{req=%lu|rply=%lu|srv=%lu|fn=%lu|length=%lu|slot=%lu]\n", + BCM_MSG_GET_FIELD(msg, REQ), BCM_MSG_GET_FIELD(msg, RPLY), + BCM_MSG_GET_FIELD(msg, SVC), BCM_MSG_GET_FIELD(msg, FUNC), + BCM_MSG_GET_FIELD(msg, LENGTH), BCM_MSG_GET_FIELD(msg, SLOT)); + + bcm74110_tx_writel(mbox, msg, BCM_MBOX_WDATA); + + return 0; +} + +#define BCM_MBOX_LINK_TRAINING_RETRIES 5 +static int bcm74110_mbox_link_training(struct bcm74110_mbox *mbox) +{ + int ret, retries = 0; + u32 msg = 0, orig_len = 0, len = BCM_LINK_CODE0; + + do { + switch (len) { + case 0: + retries++; + dev_warn(&mbox->pdev->dev, + "Link train failed, trying again... %d\n", + retries); + if (retries > BCM_MBOX_LINK_TRAINING_RETRIES) + return -EINVAL; + len = BCM_LINK_CODE0; + fallthrough; + case BCM_LINK_CODE0: + case BCM_LINK_CODE1: + case BCM_LINK_CODE2: + msg = bcm74110_mbox_create_msg(1, 0, BCM_MSG_SVC_INIT, + BCM_MSG_FUNC_LINK_START, + len, BCM_MSG_SVC_INIT); + break; + default: + break; + } + + bcm74110_mbox_tx_msg(mbox, msg); + + /* No response expected for LINK_CODE2 */ + if (len == BCM_LINK_CODE2) + return 0; + + orig_len = len; + + ret = bcm74110_rx_pop_init_msg_block(mbox, + BCM_MSG_GET_FIELD(msg, FUNC), + &msg); + if (ret) { + len = 0; + continue; + } + + if ((BCM_MSG_GET_FIELD(msg, SVC) != BCM_MSG_SVC_INIT) || + (BCM_MSG_GET_FIELD(msg, FUNC) != BCM_MSG_FUNC_LINK_START) || + (BCM_MSG_GET_FIELD(msg, SLOT) != 0) || + (BCM_MSG_GET_FIELD(msg, RPLY) != 1) || + (BCM_MSG_GET_FIELD(msg, REQ) != 0)) { + len = 0; + continue; + } + + len = BCM_MSG_GET_FIELD(msg, LENGTH); + + /* Make sure sequence is good */ + if (len != (orig_len + 1)) { + len = 0; + continue; + } + } while (1); + + return -EINVAL; +} + +static int bcm74110_mbox_tx_msg_and_wait_ack(struct bcm74110_mbox *mbox, u32 msg) +{ + int ret; + u32 recv_msg; + + ret = bcm74110_mbox_tx_msg(mbox, msg); + if (ret) + return ret; + + ret = bcm74110_rx_pop_init_msg_block(mbox, BCM_MSG_GET_FIELD(msg, FUNC), + &recv_msg); + if (ret) + return ret; + + /* + * Modify tx message to verify rx ack. + * Flip RPLY/REQ for synchronous messages + */ + if (BCM_MSG_GET_FIELD(msg, REQ) == 1) { + BCM_MSG_SET_FIELD(msg, RPLY, 1); + BCM_MSG_SET_FIELD(msg, REQ, 0); + } + + if (msg != recv_msg) { + dev_err(&mbox->pdev->dev, "Found ack, but ack is invalid\n"); + return -EINVAL; + } + + return 0; +} + +/* Each index points to 0x100 of HAB MEM. IDX size counts from 0 */ +#define BCM_MBOX_HAB_MEM_IDX_START 0x30 +#define BCM_MBOX_HAB_MEM_IDX_SIZE 0x0 +static int bcm74110_mbox_shmem_init(struct bcm74110_mbox *mbox) +{ + u32 msg = 0; + int ret; + + msg = bcm74110_mbox_create_msg(1, 0, BCM_MSG_SVC_INIT, + BCM_MSG_FUNC_SHMEM_STOP, + 0, BCM_MSG_SVC_INIT); + ret = bcm74110_mbox_tx_msg_and_wait_ack(mbox, msg); + if (ret) + return -EINVAL; + + msg = bcm74110_mbox_create_msg(1, 0, BCM_MSG_SVC_INIT, + BCM_MSG_FUNC_SHMEM_TX, + BCM_MBOX_HAB_MEM_IDX_START, + BCM_MBOX_HAB_MEM_IDX_SIZE); + ret = bcm74110_mbox_tx_msg_and_wait_ack(mbox, msg); + if (ret) + return -EINVAL; + + msg = bcm74110_mbox_create_msg(1, 0, BCM_MSG_SVC_INIT, + BCM_MSG_FUNC_SHMEM_RX, + BCM_MBOX_HAB_MEM_IDX_START, + BCM_MBOX_HAB_MEM_IDX_SIZE); + ret = bcm74110_mbox_tx_msg_and_wait_ack(mbox, msg); + if (ret) + return -EINVAL; + + return 0; +} + +static int bcm74110_mbox_init(struct bcm74110_mbox *mbox) +{ + int ret = 0; + + /* Disable queues tx/rx */ + bcm74110_tx_writel(mbox, 0x0, BCM_MBOX_CTRL); + + /* Clear status & restart tx/rx*/ + bcm74110_tx_writel(mbox, BCM_MBOX_CTRL_EN | BCM_MBOX_CTRL_CLR, + BCM_MBOX_CTRL); + + /* Unmask irq */ + bcm74110_irq_writel(mbox, BCM_MBOX_IRQ_NOT_EMPTY, BCM_MBOX_IRQ_MASK_CLEAR); + + ret = bcm74110_mbox_link_training(mbox); + if (ret) { + dev_err(&mbox->pdev->dev, "Training failed\n"); + return ret; + } + + return bcm74110_mbox_shmem_init(mbox); +} + +static int bcm74110_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct bcm74110_mbox_chan *chan_priv = chan->con_priv; + u32 msg; + + switch (chan_priv->type) { + case BCM_MSG_SVC_PMC: + case BCM_MSG_SVC_SCMI: + case BCM_MSG_SVC_DPFE: + msg = bcm74110_mbox_create_msg(1, 0, chan_priv->type, 0, + 128 + 28, chan_priv->slot); + break; + default: + return -EINVAL; + } + + return bcm74110_mbox_tx_msg(chan_priv->mbox, msg); +} + +static int bcm74110_mbox_chan_startup(struct mbox_chan *chan) +{ + struct bcm74110_mbox_chan *chan_priv = chan->con_priv; + + chan_priv->en = true; + + return 0; +} + +static void bcm74110_mbox_chan_shutdown(struct mbox_chan *chan) +{ + struct bcm74110_mbox_chan *chan_priv = chan->con_priv; + + chan_priv->en = false; +} + +static const struct mbox_chan_ops bcm74110_mbox_chan_ops = { + .send_data = bcm74110_mbox_send_data, + .startup = bcm74110_mbox_chan_startup, + .shutdown = bcm74110_mbox_chan_shutdown, +}; + +static void bcm74110_mbox_shutdown(struct platform_device *pdev) +{ + struct bcm74110_mbox *mbox = dev_get_drvdata(&pdev->dev); + u32 msg; + + msg = bcm74110_mbox_create_msg(1, 0, BCM_MSG_SVC_INIT, + BCM_MSG_FUNC_LINK_STOP, + 0, 0); + + bcm74110_mbox_tx_msg_and_wait_ack(mbox, msg); + + /* Even if we don't receive ACK, lets shut it down */ + + bcm74110_mbox_mask_and_clear(mbox); + + /* Disable queues tx/rx */ + bcm74110_tx_writel(mbox, 0x0, BCM_MBOX_CTRL); + + /* Flush queues */ + bcm74110_rx_flush_msg(mbox); +} + +static struct mbox_chan *bcm74110_mbox_of_xlate(struct mbox_controller *cntrl, + const struct of_phandle_args *p) +{ + struct bcm74110_mbox *mbox = bcm74110_mbox_from_cntrl(cntrl); + struct device *dev = &mbox->pdev->dev; + struct bcm74110_mbox_chan *chan_priv; + int slot, type; + + if (p->args_count != 2) { + dev_err(dev, "Invalid arguments\n"); + return ERR_PTR(-EINVAL); + } + + type = p->args[0]; + slot = p->args[1]; + + switch (type) { + case BCM_MSG_SVC_PMC: + case BCM_MSG_SVC_SCMI: + case BCM_MSG_SVC_DPFE: + if (slot > BCM_MBOX_HAB_MEM_IDX_SIZE) { + dev_err(dev, "Not enough shared memory\n"); + return ERR_PTR(-EINVAL); + } + chan_priv = cntrl->chans[type].con_priv; + chan_priv->slot = slot; + chan_priv->type = type; + break; + default: + dev_err(dev, "Invalid channel type: %d\n", type); + return ERR_PTR(-EINVAL); + } + + return &cntrl->chans[type]; +} + +static int bcm74110_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm74110_mbox *mbox; + int i, ret; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + mbox->pdev = pdev; + platform_set_drvdata(pdev, mbox); + + mbox->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->base)) + return dev_err_probe(dev, PTR_ERR(mbox->base), "Failed to iomap\n"); + + ret = of_property_read_u32(dev->of_node, "brcm,tx", &mbox->tx_chan); + if (ret) + return dev_err_probe(dev, ret, "Failed to find tx channel\n"); + + ret = of_property_read_u32(dev->of_node, "brcm,rx", &mbox->rx_chan); + if (ret) + return dev_err_probe(dev, ret, "Failed to find rx channel\n"); + + mbox->rx_irq = platform_get_irq(pdev, 0); + if (mbox->rx_irq < 0) + return mbox->rx_irq; + + INIT_LIST_HEAD(&mbox->rx_svc_init_list); + spin_lock_init(&mbox->rx_svc_list_lock); + bcm74110_mbox_mask_and_clear(mbox); + + ret = devm_request_irq(dev, mbox->rx_irq, bcm74110_mbox_isr, + IRQF_NO_SUSPEND, pdev->name, mbox); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq\n"); + + mbox->controller.ops = &bcm74110_mbox_chan_ops; + mbox->controller.dev = dev; + mbox->controller.num_chans = BCM_MSG_SVC_MAX; + mbox->controller.of_xlate = &bcm74110_mbox_of_xlate; + mbox->controller.chans = devm_kcalloc(dev, BCM_MSG_SVC_MAX, + sizeof(*mbox->controller.chans), + GFP_KERNEL); + if (!mbox->controller.chans) + return -ENOMEM; + + mbox->mbox_chan = devm_kcalloc(dev, BCM_MSG_SVC_MAX, + sizeof(*mbox->mbox_chan), + GFP_KERNEL); + if (!mbox->mbox_chan) + return -ENOMEM; + + for (i = 0; i < BCM_MSG_SVC_MAX; i++) { + mbox->mbox_chan[i].mbox = mbox; + mbox->controller.chans[i].con_priv = &mbox->mbox_chan[i]; + } + + ret = devm_mbox_controller_register(dev, &mbox->controller); + if (ret) + return ret; + + ret = bcm74110_mbox_init(mbox); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id bcm74110_mbox_of_match[] = { + { .compatible = "brcm,bcm74110-mbox", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, bcm74110_mbox_of_match); + +static struct platform_driver bcm74110_mbox_driver = { + .driver = { + .name = "bcm74110-mbox", + .of_match_table = bcm74110_mbox_of_match, + }, + .probe = bcm74110_mbox_probe, + .shutdown = bcm74110_mbox_shutdown, +}; +module_platform_driver(bcm74110_mbox_driver); + +MODULE_AUTHOR("Justin Chen <justin.chen@broadcom.com>"); +MODULE_DESCRIPTION("BCM74110 mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/cix-mailbox.c b/drivers/mailbox/cix-mailbox.c new file mode 100644 index 000000000000..5bb1416c26a5 --- /dev/null +++ b/drivers/mailbox/cix-mailbox.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 Cix Technology Group Co., Ltd. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "mailbox.h" + +/* + * The maximum transmission size is 32 words or 128 bytes. + */ +#define CIX_MBOX_MSG_WORDS 32 /* Max length = 32 words */ +#define CIX_MBOX_MSG_LEN_MASK 0x7fL /* Max length = 128 bytes */ + +/* [0~7] Fast channel + * [8] doorbell base channel + * [9]fifo base channel + * [10] register base channel + */ +#define CIX_MBOX_FAST_IDX 7 +#define CIX_MBOX_DB_IDX 8 +#define CIX_MBOX_FIFO_IDX 9 +#define CIX_MBOX_REG_IDX 10 +#define CIX_MBOX_CHANS 11 + +/* Register define */ +#define CIX_REG_MSG(n) (0x0 + 0x4*(n)) /* 0x0~0x7c */ +#define CIX_REG_DB_ACK CIX_REG_MSG(CIX_MBOX_MSG_WORDS) /* 0x80 */ +#define CIX_ERR_COMP (CIX_REG_DB_ACK + 0x4) /* 0x84 */ +#define CIX_ERR_COMP_CLR (CIX_REG_DB_ACK + 0x8) /* 0x88 */ +#define CIX_REG_F_INT(IDX) (CIX_ERR_COMP_CLR + 0x4*(IDX+1)) /* 0x8c~0xa8 */ +#define CIX_FIFO_WR (CIX_REG_F_INT(CIX_MBOX_FAST_IDX+1)) /* 0xac */ +#define CIX_FIFO_RD (CIX_FIFO_WR + 0x4) /* 0xb0 */ +#define CIX_FIFO_STAS (CIX_FIFO_WR + 0x8) /* 0xb4 */ +#define CIX_FIFO_WM (CIX_FIFO_WR + 0xc) /* 0xb8 */ +#define CIX_INT_ENABLE (CIX_FIFO_WR + 0x10) /* 0xbc */ +#define CIX_INT_ENABLE_SIDE_B (CIX_FIFO_WR + 0x14) /* 0xc0 */ +#define CIX_INT_CLEAR (CIX_FIFO_WR + 0x18) /* 0xc4 */ +#define CIX_INT_STATUS (CIX_FIFO_WR + 0x1c) /* 0xc8 */ +#define CIX_FIFO_RST (CIX_FIFO_WR + 0x20) /* 0xcc */ + +#define CIX_MBOX_TX 0 +#define CIX_MBOX_RX 1 + +#define CIX_DB_INT_BIT BIT(0) +#define CIX_DB_ACK_INT_BIT BIT(1) + +#define CIX_FIFO_WM_DEFAULT CIX_MBOX_MSG_WORDS +#define CIX_FIFO_STAS_WMK BIT(0) +#define CIX_FIFO_STAS_FULL BIT(1) +#define CIX_FIFO_STAS_EMPTY BIT(2) +#define CIX_FIFO_STAS_UFLOW BIT(3) +#define CIX_FIFO_STAS_OFLOW BIT(4) + +#define CIX_FIFO_RST_BIT BIT(0) + +#define CIX_DB_INT BIT(0) +#define CIX_ACK_INT BIT(1) +#define CIX_FIFO_FULL_INT BIT(2) +#define CIX_FIFO_EMPTY_INT BIT(3) +#define CIX_FIFO_WM01_INT BIT(4) +#define CIX_FIFO_WM10_INT BIT(5) +#define CIX_FIFO_OFLOW_INT BIT(6) +#define CIX_FIFO_UFLOW_INT BIT(7) +#define CIX_FIFO_N_EMPTY_INT BIT(8) +#define CIX_FAST_CH_INT(IDX) BIT((IDX)+9) + +#define CIX_SHMEM_OFFSET 0x80 + +enum cix_mbox_chan_type { + CIX_MBOX_TYPE_DB, + CIX_MBOX_TYPE_REG, + CIX_MBOX_TYPE_FIFO, + CIX_MBOX_TYPE_FAST, +}; + +struct cix_mbox_con_priv { + enum cix_mbox_chan_type type; + struct mbox_chan *chan; + int index; +}; + +struct cix_mbox_priv { + struct device *dev; + int irq; + int dir; + void __iomem *base; /* region for mailbox */ + struct cix_mbox_con_priv con_priv[CIX_MBOX_CHANS]; + struct mbox_chan mbox_chans[CIX_MBOX_CHANS]; + struct mbox_controller mbox; + bool use_shmem; +}; + +/* + * The CIX mailbox supports four types of transfers: + * CIX_MBOX_TYPE_DB, CIX_MBOX_TYPE_FAST, CIX_MBOX_TYPE_REG, and CIX_MBOX_TYPE_FIFO. + * For the REG and FIFO types of transfers, the message format is as follows: + */ +union cix_mbox_msg_reg_fifo { + u32 length; /* unit is byte */ + u32 buf[CIX_MBOX_MSG_WORDS]; /* buf[0] must be the byte length of this array */ +}; + +static struct cix_mbox_priv *to_cix_mbox_priv(struct mbox_controller *mbox) +{ + return container_of(mbox, struct cix_mbox_priv, mbox); +} + +static void cix_mbox_write(struct cix_mbox_priv *priv, u32 val, u32 offset) +{ + if (priv->use_shmem) + iowrite32(val, priv->base + offset - CIX_SHMEM_OFFSET); + else + iowrite32(val, priv->base + offset); +} + +static u32 cix_mbox_read(struct cix_mbox_priv *priv, u32 offset) +{ + if (priv->use_shmem) + return ioread32(priv->base + offset - CIX_SHMEM_OFFSET); + else + return ioread32(priv->base + offset); +} + +static bool mbox_fifo_empty(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + + return ((cix_mbox_read(priv, CIX_FIFO_STAS) & CIX_FIFO_STAS_EMPTY) ? true : false); +} + +/* + *The transmission unit of the CIX mailbox is word. + *The byte length should be converted into the word length. + */ +static inline u32 mbox_get_msg_size(void *msg) +{ + u32 len; + + len = ((u32 *)msg)[0] & CIX_MBOX_MSG_LEN_MASK; + return DIV_ROUND_UP(len, 4); +} + +static int cix_mbox_send_data_db(struct mbox_chan *chan, void *data) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + + /* trigger doorbell irq */ + cix_mbox_write(priv, CIX_DB_INT_BIT, CIX_REG_DB_ACK); + + return 0; +} + +static int cix_mbox_send_data_reg(struct mbox_chan *chan, void *data) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + union cix_mbox_msg_reg_fifo *msg = data; + u32 len, i; + + if (!data) + return -EINVAL; + + len = mbox_get_msg_size(data); + for (i = 0; i < len; i++) + cix_mbox_write(priv, msg->buf[i], CIX_REG_MSG(i)); + + /* trigger doorbell irq */ + cix_mbox_write(priv, CIX_DB_INT_BIT, CIX_REG_DB_ACK); + + return 0; +} + +static int cix_mbox_send_data_fifo(struct mbox_chan *chan, void *data) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + union cix_mbox_msg_reg_fifo *msg = data; + u32 len, val, i; + + if (!data) + return -EINVAL; + + len = mbox_get_msg_size(data); + cix_mbox_write(priv, len, CIX_FIFO_WM); + for (i = 0; i < len; i++) + cix_mbox_write(priv, msg->buf[i], CIX_FIFO_WR); + + /* Enable fifo empty interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val |= CIX_FIFO_EMPTY_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE); + + return 0; +} + +static int cix_mbox_send_data_fast(struct mbox_chan *chan, void *data) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + u32 *arg = (u32 *)data; + int index = cp->index; + + if (!data) + return -EINVAL; + + if (index < 0 || index > CIX_MBOX_FAST_IDX) { + dev_err(priv->dev, "Invalid Mbox index %d\n", index); + return -EINVAL; + } + + cix_mbox_write(priv, arg[0], CIX_REG_F_INT(index)); + + return 0; +} + +static int cix_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + + if (priv->dir != CIX_MBOX_TX) { + dev_err(priv->dev, "Invalid Mbox dir %d\n", priv->dir); + return -EINVAL; + } + + switch (cp->type) { + case CIX_MBOX_TYPE_DB: + cix_mbox_send_data_db(chan, data); + break; + case CIX_MBOX_TYPE_REG: + cix_mbox_send_data_reg(chan, data); + break; + case CIX_MBOX_TYPE_FIFO: + cix_mbox_send_data_fifo(chan, data); + break; + case CIX_MBOX_TYPE_FAST: + cix_mbox_send_data_fast(chan, data); + break; + default: + dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); + return -EINVAL; + } + return 0; +} + +static void cix_mbox_isr_db(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + u32 int_status; + + int_status = cix_mbox_read(priv, CIX_INT_STATUS); + + if (priv->dir == CIX_MBOX_RX) { + /* rx interrupt is triggered */ + if (int_status & CIX_DB_INT) { + cix_mbox_write(priv, CIX_DB_INT, CIX_INT_CLEAR); + mbox_chan_received_data(chan, NULL); + /* trigger ack interrupt */ + cix_mbox_write(priv, CIX_DB_ACK_INT_BIT, CIX_REG_DB_ACK); + } + } else { + /* tx ack interrupt is triggered */ + if (int_status & CIX_ACK_INT) { + cix_mbox_write(priv, CIX_ACK_INT, CIX_INT_CLEAR); + mbox_chan_received_data(chan, NULL); + } + } +} + +static void cix_mbox_isr_reg(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + u32 int_status; + + int_status = cix_mbox_read(priv, CIX_INT_STATUS); + + if (priv->dir == CIX_MBOX_RX) { + /* rx interrupt is triggered */ + if (int_status & CIX_DB_INT) { + u32 data[CIX_MBOX_MSG_WORDS], len, i; + + cix_mbox_write(priv, CIX_DB_INT, CIX_INT_CLEAR); + data[0] = cix_mbox_read(priv, CIX_REG_MSG(0)); + len = mbox_get_msg_size(data); + for (i = 1; i < len; i++) + data[i] = cix_mbox_read(priv, CIX_REG_MSG(i)); + + /* trigger ack interrupt */ + cix_mbox_write(priv, CIX_DB_ACK_INT_BIT, CIX_REG_DB_ACK); + mbox_chan_received_data(chan, data); + } + } else { + /* tx ack interrupt is triggered */ + if (int_status & CIX_ACK_INT) { + cix_mbox_write(priv, CIX_ACK_INT, CIX_INT_CLEAR); + mbox_chan_txdone(chan, 0); + } + } +} + +static void cix_mbox_isr_fifo(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + u32 int_status, status; + + int_status = cix_mbox_read(priv, CIX_INT_STATUS); + + if (priv->dir == CIX_MBOX_RX) { + /* FIFO waterMark interrupt is generated */ + if (int_status & (CIX_FIFO_FULL_INT | CIX_FIFO_WM01_INT)) { + u32 data[CIX_MBOX_MSG_WORDS] = { 0 }, i = 0; + + cix_mbox_write(priv, (CIX_FIFO_FULL_INT | CIX_FIFO_WM01_INT), + CIX_INT_CLEAR); + do { + data[i++] = cix_mbox_read(priv, CIX_FIFO_RD); + } while (!mbox_fifo_empty(chan) && i < CIX_MBOX_MSG_WORDS); + + mbox_chan_received_data(chan, data); + } + /* FIFO underflow is generated */ + if (int_status & CIX_FIFO_UFLOW_INT) { + status = cix_mbox_read(priv, CIX_FIFO_STAS); + dev_err(priv->dev, "fifo underflow: int_stats %d\n", status); + cix_mbox_write(priv, CIX_FIFO_UFLOW_INT, CIX_INT_CLEAR); + } + } else { + /* FIFO empty interrupt is generated */ + if (int_status & CIX_FIFO_EMPTY_INT) { + u32 val; + + cix_mbox_write(priv, CIX_FIFO_EMPTY_INT, CIX_INT_CLEAR); + /* Disable empty irq*/ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val &= ~CIX_FIFO_EMPTY_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE); + mbox_chan_txdone(chan, 0); + } + /* FIFO overflow is generated */ + if (int_status & CIX_FIFO_OFLOW_INT) { + status = cix_mbox_read(priv, CIX_FIFO_STAS); + dev_err(priv->dev, "fifo overlow: int_stats %d\n", status); + cix_mbox_write(priv, CIX_FIFO_OFLOW_INT, CIX_INT_CLEAR); + } + } +} + +static void cix_mbox_isr_fast(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + u32 int_status, data; + + /* no irq will be trigger for TX dir mbox */ + if (priv->dir != CIX_MBOX_RX) + return; + + int_status = cix_mbox_read(priv, CIX_INT_STATUS); + + if (int_status & CIX_FAST_CH_INT(cp->index)) { + cix_mbox_write(priv, CIX_FAST_CH_INT(cp->index), CIX_INT_CLEAR); + data = cix_mbox_read(priv, CIX_REG_F_INT(cp->index)); + mbox_chan_received_data(chan, &data); + } +} + +static irqreturn_t cix_mbox_isr(int irq, void *arg) +{ + struct mbox_chan *chan = arg; + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + + switch (cp->type) { + case CIX_MBOX_TYPE_DB: + cix_mbox_isr_db(chan); + break; + case CIX_MBOX_TYPE_REG: + cix_mbox_isr_reg(chan); + break; + case CIX_MBOX_TYPE_FIFO: + cix_mbox_isr_fifo(chan); + break; + case CIX_MBOX_TYPE_FAST: + cix_mbox_isr_fast(chan); + break; + default: + dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int cix_mbox_startup(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + int index = cp->index, ret; + u32 val; + + ret = request_irq(priv->irq, cix_mbox_isr, 0, + dev_name(priv->dev), chan); + if (ret) { + dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq); + return ret; + } + + switch (cp->type) { + case CIX_MBOX_TYPE_DB: + /* Overwrite txdone_method for DB channel */ + chan->txdone_method = TXDONE_BY_ACK; + fallthrough; + case CIX_MBOX_TYPE_REG: + if (priv->dir == CIX_MBOX_TX) { + /* Enable ACK interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val |= CIX_ACK_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE); + } else { + /* Enable Doorbell interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val |= CIX_DB_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + case CIX_MBOX_TYPE_FIFO: + /* reset fifo */ + cix_mbox_write(priv, CIX_FIFO_RST_BIT, CIX_FIFO_RST); + /* set default watermark */ + cix_mbox_write(priv, CIX_FIFO_WM_DEFAULT, CIX_FIFO_WM); + if (priv->dir == CIX_MBOX_TX) { + /* Enable fifo overflow interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val |= CIX_FIFO_OFLOW_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE); + } else { + /* Enable fifo full/underflow interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val |= CIX_FIFO_UFLOW_INT|CIX_FIFO_WM01_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + case CIX_MBOX_TYPE_FAST: + /* Only RX channel has intterupt */ + if (priv->dir == CIX_MBOX_RX) { + if (index < 0 || index > CIX_MBOX_FAST_IDX) { + dev_err(priv->dev, "Invalid index %d\n", index); + ret = -EINVAL; + goto failed; + } + /* enable fast channel interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val |= CIX_FAST_CH_INT(index); + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + default: + dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); + ret = -EINVAL; + goto failed; + } + return 0; + +failed: + free_irq(priv->irq, chan); + return ret; +} + +static void cix_mbox_shutdown(struct mbox_chan *chan) +{ + struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); + struct cix_mbox_con_priv *cp = chan->con_priv; + int index = cp->index; + u32 val; + + switch (cp->type) { + case CIX_MBOX_TYPE_DB: + case CIX_MBOX_TYPE_REG: + if (priv->dir == CIX_MBOX_TX) { + /* Disable ACK interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val &= ~CIX_ACK_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE); + } else if (priv->dir == CIX_MBOX_RX) { + /* Disable Doorbell interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val &= ~CIX_DB_INT; + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + case CIX_MBOX_TYPE_FIFO: + if (priv->dir == CIX_MBOX_TX) { + /* Disable empty/fifo overflow irq*/ + val = cix_mbox_read(priv, CIX_INT_ENABLE); + val &= ~(CIX_FIFO_EMPTY_INT | CIX_FIFO_OFLOW_INT); + cix_mbox_write(priv, val, CIX_INT_ENABLE); + } else if (priv->dir == CIX_MBOX_RX) { + /* Disable fifo WM01/underflow interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val &= ~(CIX_FIFO_UFLOW_INT | CIX_FIFO_WM01_INT); + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + case CIX_MBOX_TYPE_FAST: + if (priv->dir == CIX_MBOX_RX) { + if (index < 0 || index > CIX_MBOX_FAST_IDX) { + dev_err(priv->dev, "Invalid index %d\n", index); + break; + } + /* Disable fast channel interrupt */ + val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); + val &= ~CIX_FAST_CH_INT(index); + cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); + } + break; + + default: + dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); + break; + } + + free_irq(priv->irq, chan); +} + +static const struct mbox_chan_ops cix_mbox_chan_ops = { + .send_data = cix_mbox_send_data, + .startup = cix_mbox_startup, + .shutdown = cix_mbox_shutdown, +}; + +static void cix_mbox_init(struct cix_mbox_priv *priv) +{ + struct cix_mbox_con_priv *cp; + int i; + + for (i = 0; i < CIX_MBOX_CHANS; i++) { + cp = &priv->con_priv[i]; + cp->index = i; + cp->chan = &priv->mbox_chans[i]; + priv->mbox_chans[i].con_priv = cp; + if (cp->index <= CIX_MBOX_FAST_IDX) + cp->type = CIX_MBOX_TYPE_FAST; + if (cp->index == CIX_MBOX_DB_IDX) + cp->type = CIX_MBOX_TYPE_DB; + if (cp->index == CIX_MBOX_FIFO_IDX) + cp->type = CIX_MBOX_TYPE_FIFO; + if (cp->index == CIX_MBOX_REG_IDX) + cp->type = CIX_MBOX_TYPE_REG; + } +} + +static int cix_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cix_mbox_priv *priv; + struct resource *res; + const char *dir_str; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* + * The first 0x80 bytes of the register space of the cix mailbox controller + * can be used as shared memory for clients. When this shared memory is in + * use, the base address of the mailbox is offset by 0x80. Therefore, when + * performing subsequent read/write operations, it is necessary to subtract + * the offset CIX_SHMEM_OFFSET. + * + * When the base address of the mailbox is offset by 0x80, it indicates + * that shmem is in use. + */ + priv->use_shmem = !!(res->start & CIX_SHMEM_OFFSET); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return priv->irq; + + if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) { + dev_err(priv->dev, "cix,mbox_dir property not found\n"); + return -EINVAL; + } + + if (!strcmp(dir_str, "tx")) + priv->dir = 0; + else if (!strcmp(dir_str, "rx")) + priv->dir = 1; + else { + dev_err(priv->dev, "cix,mbox_dir=%s is not expected\n", dir_str); + return -EINVAL; + } + + cix_mbox_init(priv); + + priv->mbox.dev = dev; + priv->mbox.ops = &cix_mbox_chan_ops; + priv->mbox.chans = priv->mbox_chans; + priv->mbox.txdone_irq = true; + priv->mbox.num_chans = CIX_MBOX_CHANS; + priv->mbox.of_xlate = NULL; + + platform_set_drvdata(pdev, priv); + ret = devm_mbox_controller_register(dev, &priv->mbox); + if (ret) + dev_err(dev, "Failed to register mailbox %d\n", ret); + + return ret; +} + +static const struct of_device_id cix_mbox_dt_ids[] = { + { .compatible = "cix,sky1-mbox" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cix_mbox_dt_ids); + +static struct platform_driver cix_mbox_driver = { + .probe = cix_mbox_probe, + .driver = { + .name = "cix_mbox", + .of_match_table = cix_mbox_dt_ids, + }, +}; + +static int __init cix_mailbox_init(void) +{ + return platform_driver_register(&cix_mbox_driver); +} +arch_initcall(cix_mailbox_init); + +MODULE_AUTHOR("Cix Technology Group Co., Ltd."); +MODULE_DESCRIPTION("CIX mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index ab4e8d1954a1..532929916e99 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -390,7 +390,7 @@ static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data) task = kzalloc(sizeof(*task), GFP_ATOMIC); if (!task) { - __pm_runtime_put_autosuspend(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); return -ENOMEM; } @@ -440,7 +440,7 @@ static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data) list_move_tail(&task->list_entry, &thread->task_busy_list); pm_runtime_mark_last_busy(cmdq->mbox.dev); - __pm_runtime_put_autosuspend(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); return 0; } @@ -488,7 +488,7 @@ done: spin_unlock_irqrestore(&thread->chan->lock, flags); pm_runtime_mark_last_busy(cmdq->mbox.dev); - __pm_runtime_put_autosuspend(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); } static int cmdq_mbox_flush(struct mbox_chan *chan, unsigned long timeout) @@ -528,7 +528,7 @@ static int cmdq_mbox_flush(struct mbox_chan *chan, unsigned long timeout) out: spin_unlock_irqrestore(&thread->chan->lock, flags); pm_runtime_mark_last_busy(cmdq->mbox.dev); - __pm_runtime_put_autosuspend(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); return 0; @@ -543,7 +543,7 @@ wait: return -EFAULT; } pm_runtime_mark_last_busy(cmdq->mbox.dev); - __pm_runtime_put_autosuspend(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); return 0; } diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index f6714c233f5a..0a00719b2482 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -306,6 +306,22 @@ static void pcc_chan_acknowledge(struct pcc_chan_info *pchan) pcc_chan_reg_read_modify_write(&pchan->db); } +static void *write_response(struct pcc_chan_info *pchan) +{ + struct pcc_header pcc_header; + void *buffer; + int data_len; + + memcpy_fromio(&pcc_header, pchan->chan.shmem, + sizeof(pcc_header)); + data_len = pcc_header.length - sizeof(u32) + sizeof(struct pcc_header); + + buffer = pchan->chan.rx_alloc(pchan->chan.mchan->cl, data_len); + if (buffer != NULL) + memcpy_fromio(buffer, pchan->chan.shmem, data_len); + return buffer; +} + /** * pcc_mbox_irq - PCC mailbox interrupt handler * @irq: interrupt number @@ -317,6 +333,8 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) { struct pcc_chan_info *pchan; struct mbox_chan *chan = p; + struct pcc_header *pcc_header = chan->active_req; + void *handle = NULL; pchan = chan->con_priv; @@ -340,7 +358,17 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) * required to avoid any possible race in updatation of this flag. */ pchan->chan_in_use = false; - mbox_chan_received_data(chan, NULL); + + if (pchan->chan.rx_alloc) + handle = write_response(pchan); + + if (chan->active_req) { + pcc_header = chan->active_req; + if (pcc_header->flags & PCC_CMD_COMPLETION_NOTIFY) + mbox_chan_txdone(chan, 0); + } + + mbox_chan_received_data(chan, handle); pcc_chan_acknowledge(pchan); @@ -384,9 +412,24 @@ pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) pcc_mchan = &pchan->chan; pcc_mchan->shmem = acpi_os_ioremap(pcc_mchan->shmem_base_addr, pcc_mchan->shmem_size); - if (pcc_mchan->shmem) - return pcc_mchan; + if (!pcc_mchan->shmem) + goto err; + + pcc_mchan->manage_writes = false; + + /* This indicates that the channel is ready to accept messages. + * This needs to happen after the channel has registered + * its callback. There is no access point to do that in + * the mailbox API. That implies that the mailbox client must + * have set the allocate callback function prior to + * sending any messages. + */ + if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + pcc_chan_reg_read_modify_write(&pchan->cmd_update); + + return pcc_mchan; +err: mbox_free_channel(chan); return ERR_PTR(-ENXIO); } @@ -417,8 +460,38 @@ void pcc_mbox_free_channel(struct pcc_mbox_chan *pchan) } EXPORT_SYMBOL_GPL(pcc_mbox_free_channel); +static int pcc_write_to_buffer(struct mbox_chan *chan, void *data) +{ + struct pcc_chan_info *pchan = chan->con_priv; + struct pcc_mbox_chan *pcc_mbox_chan = &pchan->chan; + struct pcc_header *pcc_header = data; + + if (!pchan->chan.manage_writes) + return 0; + + /* The PCC header length includes the command field + * but not the other values from the header. + */ + int len = pcc_header->length - sizeof(u32) + sizeof(struct pcc_header); + u64 val; + + pcc_chan_reg_read(&pchan->cmd_complete, &val); + if (!val) { + pr_info("%s pchan->cmd_complete not set", __func__); + return -1; + } + memcpy_toio(pcc_mbox_chan->shmem, data, len); + return 0; +} + + /** - * pcc_send_data - Called from Mailbox Controller code. Used + * pcc_send_data - Called from Mailbox Controller code. If + * pchan->chan.rx_alloc is set, then the command complete + * flag is checked and the data is written to the shared + * buffer io memory. + * + * If pchan->chan.rx_alloc is not set, then it is used * here only to ring the channel doorbell. The PCC client * specific read/write is done in the client driver in * order to maintain atomicity over PCC channel once @@ -434,17 +507,37 @@ static int pcc_send_data(struct mbox_chan *chan, void *data) int ret; struct pcc_chan_info *pchan = chan->con_priv; + ret = pcc_write_to_buffer(chan, data); + if (ret) + return ret; + ret = pcc_chan_reg_read_modify_write(&pchan->cmd_update); if (ret) return ret; ret = pcc_chan_reg_read_modify_write(&pchan->db); + if (!ret && pchan->plat_irq > 0) pchan->chan_in_use = true; return ret; } + +static bool pcc_last_tx_done(struct mbox_chan *chan) +{ + struct pcc_chan_info *pchan = chan->con_priv; + u64 val; + + pcc_chan_reg_read(&pchan->cmd_complete, &val); + if (!val) + return false; + else + return true; +} + + + /** * pcc_startup - Called from Mailbox Controller code. Used here * to request the interrupt. @@ -490,6 +583,7 @@ static const struct mbox_chan_ops pcc_chan_ops = { .send_data = pcc_send_data, .startup = pcc_startup, .shutdown = pcc_shutdown, + .last_tx_done = pcc_last_tx_done, }; /** diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c index ea44ffb5ce1a..d957d989c0ce 100644 --- a/drivers/mailbox/qcom-ipcc.c +++ b/drivers/mailbox/qcom-ipcc.c @@ -312,8 +312,7 @@ static int qcom_ipcc_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ipcc->irq_domain = irq_domain_create_tree(of_fwnode_handle(pdev->dev.of_node), - &qcom_ipcc_irq_ops, ipcc); + ipcc->irq_domain = irq_domain_create_tree(dev_fwnode(&pdev->dev), &qcom_ipcc_irq_ops, ipcc); if (!ipcc->irq_domain) return -ENOMEM; |