diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/mailbox | |
| download | linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip | |
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe:
- Fix a task_work add use-after-free with SQPOLL.
The sqpoll thread could pop and complete the last request while
io_req_normal_work_add() was still looking at them after the mpscq
push.
Use the same approach as DEFER_TASKRUN to protect from that, holding
an RCU read lock across the add, and have exit wait for an RCU grace
period for SQPOLL rings as well.
- CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the
big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED
rings
- Mark the source filter table as COW when cloning bpf filters, so
registering another filter on the source doesn't modify the shared
table in place
- Initialize the task context before running the BPF loop
- Requeue zcrx multishot receives stopped by a local resource
- End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit)
* tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux:
io_uring: fix task_work add use-after-free with SQPOLL
io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full
io_uring/zcrx: requeue multishot receives stopped by a local resource
io_uring: initialize task context before running the BPF loop
io_uring: zero big_cqe for aux CQEs on CQE32 rings
io_uring: fix free entry check for 32b CQEs on CQE32 rings
io_uring: only post the dummy skip CQE on CQE_MIXED rings
io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/mailbox')
46 files changed, 24606 insertions, 0 deletions
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig new file mode 100644 index 000000000..ab869769e --- /dev/null +++ b/drivers/mailbox/Kconfig @@ -0,0 +1,412 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig MAILBOX + bool "Mailbox Hardware Support" + help + Mailbox is a framework to control hardware communication between + on-chip processors through queued messages and interrupt driven + signals. Say Y if your platform supports hardware mailboxes. + +if MAILBOX + +config ARM_MHU + tristate "ARM MHU Mailbox" + depends on ARM_AMBA + help + Say Y here if you want to build the ARM MHU controller driver. + The controller has 3 mailbox channels, the last of which can be + used in Secure mode only. + +config ARM_MHU_V2 + tristate "ARM MHUv2 Mailbox" + depends on ARM_AMBA + help + Say Y here if you want to build the ARM MHUv2 controller driver, + which provides unidirectional mailboxes between processing elements. + +config ARM_MHU_V3 + tristate "ARM MHUv3 Mailbox" + depends on ARM64 || COMPILE_TEST + depends on HAS_IOMEM || COMPILE_TEST + depends on OF + help + Say Y here if you want to build the ARM MHUv3 controller driver, + which provides unidirectional mailboxes between processing elements. + + ARM MHUv3 controllers can implement a varying number of extensions + 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 + help + Mailbox driver implementation for Sophgo CV18XX SoCs. This driver + can be used to send message between different processors in SoC. Any + processer can write data in a channel, and set co-responding register + to raise interrupt to notice another processor, and it is allowed to + send data to itself. + +config EXYNOS_MBOX + tristate "Exynos Mailbox" + depends on ARCH_EXYNOS || COMPILE_TEST + help + Say Y here if you want to build the Samsung Exynos Mailbox controller + driver. The controller has 16 flag bits for hardware interrupt + generation and a shared register for passing mailbox messages. + When the controller is used by the ACPM interface the shared register + is ignored and the mailbox controller acts as a doorbell that raises + the interrupt to the ACPM firmware. + +config IMX_MBOX + tristate "i.MX Mailbox" + depends on ARCH_MXC || COMPILE_TEST + help + Mailbox implementation for i.MX Messaging Unit (MU). + +config PLATFORM_MHU + tristate "Platform MHU Mailbox" + depends on OF + depends on HAS_IOMEM + help + Say Y here if you want to build a platform specific variant MHU + controller driver. + The controller has a maximum of 3 mailbox channels, the last of + which can be used in Secure mode only. + +config PL320_MBOX + bool "ARM PL320 Mailbox" + depends on ARM_AMBA + help + An implementation of the ARM PL320 Interprocessor Communication + Mailbox (IPCM), tailored for the Calxeda Highbank. It is used to + send short messages between Highbank's A9 cores and the EnergyCore + Management Engine, primarily for cpufreq. Say Y here if you want + to use the PL320 IPCM support. + +config ARMADA_37XX_RWTM_MBOX + tristate "Armada 37xx rWTM BIU Mailbox" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + help + Mailbox implementation for communication with the the firmware + running on the Cortex-M3 rWTM secure processor of the Armada 37xx + SOC. Say Y here if you are building for such a device (for example + the Turris Mox router). + +config OMAP2PLUS_MBOX + tristate "OMAP2+ Mailbox framework support" + depends on ARCH_OMAP2PLUS || ARCH_K3 || COMPILE_TEST + help + Mailbox implementation for OMAP family chips with hardware for + interprocessor communication involving DSP, IVA1.0 and IVA2 in + OMAP2/3; or IPU, IVA HD and DSP in OMAP4/5. Say Y here if you + want to use OMAP2+ Mailbox framework support. + +config ROCKCHIP_MBOX + bool "Rockchip Soc Integrated Mailbox Support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + This driver provides support for inter-processor communication + between CPU cores and MCU processor on Some Rockchip SOCs. + Please check it that the Soc you use have Mailbox hardware. + Say Y here if you want to use the Rockchip Mailbox support. + +config PCC + bool "Platform Communication Channel Driver" + depends on ACPI + default n + help + ACPI 5.0+ spec defines a generic mode of communication + between the OS and a platform such as the BMC. This medium + (PCC) is typically used by CPPC (ACPI CPU Performance management), + RAS (ACPI reliability protocol) and MPST (ACPI Memory power + states). Select this driver if your platform implements the + PCC clients mentioned above. + +config ALTERA_MBOX + tristate "Altera Mailbox" + depends on HAS_IOMEM + help + An implementation of the Altera Mailbox soft core. It is used + to send message between processors. Say Y here if you want to use the + Altera mailbox support. + +config BCM2835_MBOX + tristate "BCM2835 Mailbox" + depends on ARCH_BCM2835 + help + An implementation of the BCM2385 Mailbox. It is used to invoke + the services of the Videocore. Say Y here if you want to use the + BCM2835 Mailbox. + +config STI_MBOX + tristate "STI Mailbox framework support" + depends on ARCH_STI && OF + help + Mailbox implementation for STMicroelectonics family chips with + hardware for interprocessor communication. + +config TI_MESSAGE_MANAGER + tristate "Texas Instruments Message Manager Driver" + depends on ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST + default ARCH_K3 + help + An implementation of Message Manager slave driver for Keystone + and K3 architecture SoCs from Texas Instruments. Message Manager + is a communication entity found on few of Texas Instrument's keystone + and K3 architecture SoCs. These may be used for communication between + multiple processors within the SoC. Select this driver if your + platform has support for the hardware block. + +config HI3660_MBOX + tristate "Hi3660 Mailbox" if EXPERT + depends on (ARCH_HISI || COMPILE_TEST) + depends on OF + default ARCH_HISI + help + An implementation of the hi3660 mailbox. It is used to send message + between application processors and other processors/MCU/DSP. Select + Y here if you want to use Hi3660 mailbox controller. + +config HI6220_MBOX + tristate "Hi6220 Mailbox" if EXPERT + depends on (ARCH_HISI || COMPILE_TEST) + depends on OF + default ARCH_HISI + help + An implementation of the hi6220 mailbox. It is used to send message + between application processors and MCU. Say Y here if you want to + build Hi6220 mailbox controller driver. + +config MAILBOX_TEST + tristate "Mailbox Test Client" + depends on OF + depends on HAS_IOMEM + help + Test client to help with testing new Controller driver + implementations. + +config POLARFIRE_SOC_MAILBOX + tristate "PolarFire SoC (MPFS) Mailbox" + depends on HAS_IOMEM + depends on MFD_SYSCON + depends on ARCH_MICROCHIP || COMPILE_TEST + help + This driver adds support for the PolarFire SoC (MPFS) mailbox controller. + + To compile this driver as a module, choose M here. the + module will be called mailbox-mpfs. + + If unsure, say N. + +config MCHP_SBI_IPC_MBOX + tristate "Microchip Inter-processor Communication (IPC) SBI driver" + depends on RISCV_SBI + depends on ARCH_MICROCHIP || COMPILE_TEST + help + Mailbox implementation for Microchip devices with an + Inter-process communication (IPC) controller. + + To compile this driver as a module, choose M here. the + module will be called mailbox-mchp-ipc-sbi. + + If unsure, say N. + +config QCOM_APCS_IPC + tristate "Qualcomm APCS IPC driver" + depends on ARCH_QCOM || COMPILE_TEST + help + Say y here to enable support for the APCS IPC mailbox driver, + providing an interface for invoking the inter-process communication + signals from the application processor to other masters. + +config TEGRA_HSP_MBOX + bool "Tegra HSP (Hardware Synchronization Primitives) Driver" + depends on ARCH_TEGRA + help + The Tegra HSP driver is used for the interprocessor communication + between different remote processors and host processors on Tegra186 + and later SoCs. Say Y here if you want to have this support. + If unsure say N. + +config XGENE_SLIMPRO_MBOX + tristate "APM SoC X-Gene SLIMpro Mailbox Controller" + depends on ARCH_XGENE + help + An implementation of the APM X-Gene Interprocessor Communication + Mailbox (IPCM) between the ARM 64-bit cores and SLIMpro controller. + It is used to send short messages between ARM64-bit cores and + the SLIMpro Management Engine, primarily for PM. Say Y here if you + want to use the APM X-Gene SLIMpro IPCM support. + +config BCM_PDC_MBOX + tristate "Broadcom FlexSparx DMA Mailbox" + depends on ARCH_BCM_IPROC || COMPILE_TEST + help + Mailbox implementation for the Broadcom FlexSparx DMA ring manager, + which provides access to various offload engines on Broadcom + SoCs, including FA2/FA+ on Northstar Plus and PDC on Northstar 2. + +config BCM_FLEXRM_MBOX + tristate "Broadcom FlexRM Mailbox" + depends on ARM64 + depends on ARCH_BCM_IPROC || COMPILE_TEST + select GENERIC_MSI_IRQ + default m if ARCH_BCM_IPROC + help + Mailbox implementation of the Broadcom FlexRM ring manager, + which provides access to various offload engines on Broadcom + SoCs. Say Y here if you want to use the Broadcom FlexRM. + +config STM32_IPCC + tristate "STM32 IPCC Mailbox" + depends on MACH_STM32MP157 || COMPILE_TEST + help + Mailbox implementation for STMicroelectonics STM32 family chips + with hardware for Inter-Processor Communication Controller (IPCC) + between processors. Say Y here if you want to have this support. + +config MTK_ADSP_MBOX + tristate "MediaTek ADSP Mailbox Controller" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + Say yes here to add support for MediaTek ADSP Mailbox Controller. + This mailbox driver is used to send notification or short message + between processors with ADSP. It will place the message to share + buffer and will access the ipc control. + +config MTK_CMDQ_MBOX + tristate "MediaTek CMDQ Mailbox Support" + depends on ARCH_MEDIATEK || COMPILE_TEST + select MTK_INFRACFG + help + Say yes here to add support for the MediaTek Command Queue (CMDQ) + mailbox driver. The CMDQ is used to help read/write registers with + critical time limitation, such as updating display configuration + during the vblank. + +config MTK_GPUEB_MBOX + tristate "MediaTek GPUEB Mailbox Support" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + The MediaTek GPUEB mailbox is used to communicate with the embedded + controller in charge of GPU frequency and power management on some + MediaTek SoCs, such as the MT8196. + Say Y or m here if you want to support the MT8196 SoC in your kernel + build. + +config MTK_VCP_MBOX + tristate "MediaTek VCP Mailbox Support" + depends on ARCH_MEDIATEK || COMPILE_TEST + help + Say yes here to add support for the MediaTek VCP mailbox driver. + The mailbox implementation provides access from the application + processor to Video Companion Processor Unit. + If unsure say N. + +config ZYNQMP_IPI_MBOX + tristate "Xilinx ZynqMP IPI Mailbox" + depends on ARCH_ZYNQMP && OF + help + Say yes here to add support for Xilinx IPI mailbox driver. + This mailbox driver is used to send notification or short message + between processors with Xilinx ZynqMP IPI. It will place the + message to the IPI buffer and will access the IPI control + registers to kick the other processor or enquire status. + +config SUN6I_MSGBOX + tristate "Allwinner sun6i/sun8i/sun9i/sun50i Message Box" + depends on ARCH_SUNXI || COMPILE_TEST + default ARCH_SUNXI + help + Mailbox implementation for the hardware message box present in + various Allwinner SoCs. This mailbox is used for communication + between the application CPUs and the power management coprocessor. + +config SPRD_MBOX + tristate "Spreadtrum Mailbox" + depends on ARCH_SPRD || COMPILE_TEST + help + Mailbox driver implementation for the Spreadtrum platform. It is used + to send message between application processors and MCU. Say Y here if + you want to build the Spreatrum mailbox controller driver. + +config QCOM_CPUCP_MBOX + tristate "Qualcomm CPUCP mailbox driver" + depends on (ARCH_QCOM || COMPILE_TEST) && 64BIT + help + Qualcomm Technologies, Inc. CPUSS Control Processor (CPUCP) mailbox + controller driver enables communication between AP and CPUCP. Say + Y here if you want to build this driver. + +config QCOM_IPCC + tristate "Qualcomm IPCC driver" + depends on ARCH_QCOM || COMPILE_TEST + help + Qualcomm Technologies, Inc. Inter-Processor Communication Controller + (IPCC) driver for MSM devices. The driver provides mailbox support for + sending interrupts to the clients. On the other hand, the driver also + acts as an interrupt controller for receiving interrupts from clients. + Say Y here if you want to build this driver. + +config THEAD_TH1520_MBOX + tristate "T-head TH1520 Mailbox" + depends on ARCH_THEAD || COMPILE_TEST + help + Mailbox driver implementation for the Thead TH-1520 platform. Enables + two cores within the SoC to communicate and coordinate by passing + messages. Could be used to communicate between E910 core, on which the + 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. + +config RISCV_SBI_MPXY_MBOX + tristate "RISC-V SBI Message Proxy (MPXY) Mailbox" + depends on RISCV_SBI + default RISCV + help + Mailbox driver implementation for RISC-V SBI Message Proxy (MPXY) + extension. This mailbox driver is used to send messages to the + remote processor through the SBI implementation (M-mode firmware + or HS-mode hypervisor). Say Y here, unless you are sure you do not + need this. + +config AXIADO_MAILBOX + tristate "Axiado mailbox driver" + depends on ARCH_AXIADO || COMPILE_TEST + depends on OF + help + Enable support for the Axiado mailbox controller. The driver provides + communication channels between the host CPU and the coprocessor on + Axiado SoCs. + If unsure, say N. + +endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile new file mode 100644 index 000000000..45dc59bb3 --- /dev/null +++ b/drivers/mailbox/Makefile @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-2.0 +# Generic MAILBOX API + +obj-$(CONFIG_MAILBOX) += mailbox.o + +obj-$(CONFIG_MAILBOX_TEST) += mailbox-test.o + +obj-$(CONFIG_ARM_MHU) += arm_mhu.o arm_mhu_db.o + +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 + +obj-$(CONFIG_IMX_MBOX) += imx-mailbox.o + +obj-$(CONFIG_ARMADA_37XX_RWTM_MBOX) += armada-37xx-rwtm-mailbox.o + +obj-$(CONFIG_PLATFORM_MHU) += platform_mhu.o + +obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o + +obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o + +obj-$(CONFIG_ROCKCHIP_MBOX) += rockchip-mailbox.o + +obj-$(CONFIG_PCC) += pcc.o + +obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o + +obj-$(CONFIG_BCM2835_MBOX) += bcm2835-mailbox.o + +obj-$(CONFIG_STI_MBOX) += mailbox-sti.o + +obj-$(CONFIG_TI_MESSAGE_MANAGER) += ti-msgmgr.o + +obj-$(CONFIG_XGENE_SLIMPRO_MBOX) += mailbox-xgene-slimpro.o + +obj-$(CONFIG_HI3660_MBOX) += hi3660-mailbox.o + +obj-$(CONFIG_HI6220_MBOX) += hi6220-mailbox.o + +obj-$(CONFIG_BCM_PDC_MBOX) += bcm-pdc-mailbox.o + +obj-$(CONFIG_BCM_FLEXRM_MBOX) += bcm-flexrm-mailbox.o + +obj-$(CONFIG_POLARFIRE_SOC_MAILBOX) += mailbox-mpfs.o + +obj-$(CONFIG_MCHP_SBI_IPC_MBOX) += mailbox-mchp-ipc-sbi.o + +obj-$(CONFIG_QCOM_APCS_IPC) += qcom-apcs-ipc-mailbox.o + +obj-$(CONFIG_TEGRA_HSP_MBOX) += tegra-hsp.o + +obj-$(CONFIG_STM32_IPCC) += stm32-ipcc.o + +obj-$(CONFIG_MTK_ADSP_MBOX) += mtk-adsp-mailbox.o + +obj-$(CONFIG_MTK_CMDQ_MBOX) += mtk-cmdq-mailbox.o + +obj-$(CONFIG_MTK_GPUEB_MBOX) += mtk-gpueb-mailbox.o + +obj-$(CONFIG_MTK_VCP_MBOX) += mtk-vcp-mailbox.o + +obj-$(CONFIG_ZYNQMP_IPI_MBOX) += zynqmp-ipi-mailbox.o + +obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o + +obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o + +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 + +obj-$(CONFIG_RISCV_SBI_MPXY_MBOX) += riscv-sbi-mpxy-mbox.o + +obj-$(CONFIG_AXIADO_MAILBOX) += axiado-mailbox.o diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c new file mode 100644 index 000000000..0950b7bce --- /dev/null +++ b/drivers/mailbox/arm_mhu.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013-2015 Fujitsu Semiconductor Ltd. + * Copyright (C) 2015 Linaro Ltd. + * Author: Jassi Brar <jaswinder.singh@linaro.org> + */ + +#include <linux/amba/bus.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> + +#define INTR_STAT_OFS 0x0 +#define INTR_SET_OFS 0x8 +#define INTR_CLR_OFS 0x10 + +#define MHU_LP_OFFSET 0x0 +#define MHU_HP_OFFSET 0x20 +#define MHU_SEC_OFFSET 0x200 +#define TX_REG_OFFSET 0x100 + +#define MHU_CHANS 3 + +struct mhu_link { + unsigned irq; + void __iomem *tx_reg; + void __iomem *rx_reg; +}; + +struct arm_mhu { + void __iomem *base; + struct mhu_link mlink[MHU_CHANS]; + struct mbox_chan chan[MHU_CHANS]; + struct mbox_controller mbox; +}; + +static irqreturn_t mhu_rx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct mhu_link *mlink = chan->con_priv; + u32 val; + + val = readl_relaxed(mlink->rx_reg + INTR_STAT_OFS); + if (!val) + return IRQ_NONE; + + mbox_chan_received_data(chan, (void *)&val); + + writel_relaxed(val, mlink->rx_reg + INTR_CLR_OFS); + + return IRQ_HANDLED; +} + +static bool mhu_last_tx_done(struct mbox_chan *chan) +{ + struct mhu_link *mlink = chan->con_priv; + u32 val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS); + + return (val == 0); +} + +static int mhu_send_data(struct mbox_chan *chan, void *data) +{ + struct mhu_link *mlink = chan->con_priv; + u32 *arg = data; + + writel_relaxed(*arg, mlink->tx_reg + INTR_SET_OFS); + + return 0; +} + +static int mhu_startup(struct mbox_chan *chan) +{ + struct mhu_link *mlink = chan->con_priv; + u32 val; + int ret; + + val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS); + writel_relaxed(val, mlink->tx_reg + INTR_CLR_OFS); + + ret = request_irq(mlink->irq, mhu_rx_interrupt, + IRQF_SHARED, "mhu_link", chan); + if (ret) { + dev_err(chan->mbox->dev, + "Unable to acquire IRQ %d\n", mlink->irq); + return ret; + } + + return 0; +} + +static void mhu_shutdown(struct mbox_chan *chan) +{ + struct mhu_link *mlink = chan->con_priv; + + free_irq(mlink->irq, chan); +} + +static const struct mbox_chan_ops mhu_ops = { + .send_data = mhu_send_data, + .startup = mhu_startup, + .shutdown = mhu_shutdown, + .last_tx_done = mhu_last_tx_done, +}; + +static int mhu_probe(struct amba_device *adev, const struct amba_id *id) +{ + int i, err; + struct arm_mhu *mhu; + struct device *dev = &adev->dev; + int mhu_reg[MHU_CHANS] = {MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET}; + + if (!of_device_is_compatible(dev->of_node, "arm,mhu")) + return -ENODEV; + + /* Allocate memory for device */ + mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL); + if (!mhu) + return -ENOMEM; + + mhu->base = devm_ioremap_resource(dev, &adev->res); + if (IS_ERR(mhu->base)) + return PTR_ERR(mhu->base); + + for (i = 0; i < MHU_CHANS; i++) { + mhu->chan[i].con_priv = &mhu->mlink[i]; + mhu->mlink[i].irq = adev->irq[i]; + mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i]; + mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET; + } + + mhu->mbox.dev = dev; + mhu->mbox.chans = &mhu->chan[0]; + mhu->mbox.num_chans = MHU_CHANS; + mhu->mbox.ops = &mhu_ops; + mhu->mbox.txdone_irq = false; + mhu->mbox.txdone_poll = true; + mhu->mbox.txpoll_period = 1; + + amba_set_drvdata(adev, mhu); + + err = devm_mbox_controller_register(dev, &mhu->mbox); + if (err) { + dev_err(dev, "Failed to register mailboxes %d\n", err); + return err; + } + + dev_info(dev, "ARM MHU Mailbox registered\n"); + return 0; +} + +static const struct amba_id mhu_ids[] = { + { + .id = 0x1bb098, + .mask = 0xffffff, + }, + { 0, 0 }, +}; +MODULE_DEVICE_TABLE(amba, mhu_ids); + +static struct amba_driver arm_mhu_driver = { + .drv = { + .name = "mhu", + }, + .id_table = mhu_ids, + .probe = mhu_probe, +}; +module_amba_driver(arm_mhu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ARM MHU Driver"); +MODULE_AUTHOR("Jassi Brar <jassisinghbrar@gmail.com>"); diff --git a/drivers/mailbox/arm_mhu_db.c b/drivers/mailbox/arm_mhu_db.c new file mode 100644 index 000000000..a39239e38 --- /dev/null +++ b/drivers/mailbox/arm_mhu_db.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013-2015 Fujitsu Semiconductor Ltd. + * Copyright (C) 2015 Linaro Ltd. + * Based on ARM MHU driver by Jassi Brar <jaswinder.singh@linaro.org> + * Copyright (C) 2020 ARM Ltd. + */ + +#include <linux/amba/bus.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> + +#define INTR_STAT_OFS 0x0 +#define INTR_SET_OFS 0x8 +#define INTR_CLR_OFS 0x10 + +#define MHU_LP_OFFSET 0x0 +#define MHU_HP_OFFSET 0x20 +#define MHU_SEC_OFFSET 0x200 +#define TX_REG_OFFSET 0x100 + +#define MHU_CHANS 3 /* Secure, Non-Secure High and Low Priority */ +#define MHU_CHAN_MAX 20 /* Max channels to save on unused RAM */ +#define MHU_NUM_DOORBELLS 32 + +struct mhu_db_link { + unsigned int irq; + void __iomem *tx_reg; + void __iomem *rx_reg; +}; + +struct arm_mhu { + void __iomem *base; + struct mhu_db_link mlink[MHU_CHANS]; + struct mbox_controller mbox; + struct device *dev; +}; + +/** + * struct mhu_db_channel - ARM MHU Mailbox allocated channel information + * + * @mhu: Pointer to parent mailbox device + * @pchan: Physical channel within which this doorbell resides in + * @doorbell: doorbell number pertaining to this channel + */ +struct mhu_db_channel { + struct arm_mhu *mhu; + unsigned int pchan; + unsigned int doorbell; +}; + +static inline struct mbox_chan * +mhu_db_mbox_to_channel(struct mbox_controller *mbox, unsigned int pchan, + unsigned int doorbell) +{ + int i; + struct mhu_db_channel *chan_info; + + for (i = 0; i < mbox->num_chans; i++) { + chan_info = mbox->chans[i].con_priv; + if (chan_info && chan_info->pchan == pchan && + chan_info->doorbell == doorbell) + return &mbox->chans[i]; + } + + return NULL; +} + +static void mhu_db_mbox_clear_irq(struct mbox_chan *chan) +{ + struct mhu_db_channel *chan_info = chan->con_priv; + void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].rx_reg; + + writel_relaxed(BIT(chan_info->doorbell), base + INTR_CLR_OFS); +} + +static unsigned int mhu_db_mbox_irq_to_pchan_num(struct arm_mhu *mhu, int irq) +{ + unsigned int pchan; + + for (pchan = 0; pchan < MHU_CHANS; pchan++) + if (mhu->mlink[pchan].irq == irq) + break; + return pchan; +} + +static struct mbox_chan * +mhu_db_mbox_irq_to_channel(struct arm_mhu *mhu, unsigned int pchan) +{ + unsigned long bits; + unsigned int doorbell; + struct mbox_chan *chan = NULL; + struct mbox_controller *mbox = &mhu->mbox; + void __iomem *base = mhu->mlink[pchan].rx_reg; + + bits = readl_relaxed(base + INTR_STAT_OFS); + if (!bits) + /* No IRQs fired in specified physical channel */ + return NULL; + + /* An IRQ has fired, find the associated channel */ + for (doorbell = 0; bits; doorbell++) { + if (!test_and_clear_bit(doorbell, &bits)) + continue; + + chan = mhu_db_mbox_to_channel(mbox, pchan, doorbell); + if (chan) + break; + dev_err(mbox->dev, + "Channel not registered: pchan: %d doorbell: %d\n", + pchan, doorbell); + } + + return chan; +} + +static irqreturn_t mhu_db_mbox_rx_handler(int irq, void *data) +{ + struct mbox_chan *chan; + struct arm_mhu *mhu = data; + unsigned int pchan = mhu_db_mbox_irq_to_pchan_num(mhu, irq); + + while (NULL != (chan = mhu_db_mbox_irq_to_channel(mhu, pchan))) { + mbox_chan_received_data(chan, NULL); + mhu_db_mbox_clear_irq(chan); + } + + return IRQ_HANDLED; +} + +static bool mhu_db_last_tx_done(struct mbox_chan *chan) +{ + struct mhu_db_channel *chan_info = chan->con_priv; + void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg; + + if (readl_relaxed(base + INTR_STAT_OFS) & BIT(chan_info->doorbell)) + return false; + + return true; +} + +static int mhu_db_send_data(struct mbox_chan *chan, void *data) +{ + struct mhu_db_channel *chan_info = chan->con_priv; + void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg; + + /* Send event to co-processor */ + writel_relaxed(BIT(chan_info->doorbell), base + INTR_SET_OFS); + + return 0; +} + +static int mhu_db_startup(struct mbox_chan *chan) +{ + mhu_db_mbox_clear_irq(chan); + return 0; +} + +static void mhu_db_shutdown(struct mbox_chan *chan) +{ + struct mhu_db_channel *chan_info = chan->con_priv; + struct mbox_controller *mbox = &chan_info->mhu->mbox; + int i; + + for (i = 0; i < mbox->num_chans; i++) + if (chan == &mbox->chans[i]) + break; + + if (mbox->num_chans == i) { + dev_warn(mbox->dev, "Request to free non-existent channel\n"); + return; + } + + /* Reset channel */ + mhu_db_mbox_clear_irq(chan); + devm_kfree(mbox->dev, chan->con_priv); + chan->con_priv = NULL; +} + +static struct mbox_chan *mhu_db_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *spec) +{ + struct arm_mhu *mhu = dev_get_drvdata(mbox->dev); + struct mhu_db_channel *chan_info; + struct mbox_chan *chan; + unsigned int pchan = spec->args[0]; + unsigned int doorbell = spec->args[1]; + int i; + + /* Bounds checking */ + if (pchan >= MHU_CHANS || doorbell >= MHU_NUM_DOORBELLS) { + dev_err(mbox->dev, + "Invalid channel requested pchan: %d doorbell: %d\n", + pchan, doorbell); + return ERR_PTR(-EINVAL); + } + + /* Is requested channel free? */ + chan = mhu_db_mbox_to_channel(mbox, pchan, doorbell); + if (chan) { + dev_err(mbox->dev, "Channel in use: pchan: %d doorbell: %d\n", + pchan, doorbell); + return ERR_PTR(-EBUSY); + } + + /* Find the first free slot */ + for (i = 0; i < mbox->num_chans; i++) + if (!mbox->chans[i].con_priv) + break; + + if (mbox->num_chans == i) { + dev_err(mbox->dev, "No free channels left\n"); + return ERR_PTR(-EBUSY); + } + + chan = &mbox->chans[i]; + + chan_info = devm_kzalloc(mbox->dev, sizeof(*chan_info), GFP_KERNEL); + if (!chan_info) + return ERR_PTR(-ENOMEM); + + chan_info->mhu = mhu; + chan_info->pchan = pchan; + chan_info->doorbell = doorbell; + + chan->con_priv = chan_info; + + dev_dbg(mbox->dev, "mbox: created channel phys: %d doorbell: %d\n", + pchan, doorbell); + + return chan; +} + +static const struct mbox_chan_ops mhu_db_ops = { + .send_data = mhu_db_send_data, + .startup = mhu_db_startup, + .shutdown = mhu_db_shutdown, + .last_tx_done = mhu_db_last_tx_done, +}; + +static int mhu_db_probe(struct amba_device *adev, const struct amba_id *id) +{ + u32 cell_count; + int i, err, max_chans; + struct arm_mhu *mhu; + struct mbox_chan *chans; + struct device *dev = &adev->dev; + struct device_node *np = dev->of_node; + int mhu_reg[MHU_CHANS] = { + MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET, + }; + + if (!of_device_is_compatible(np, "arm,mhu-doorbell")) + return -ENODEV; + + err = of_property_read_u32(np, "#mbox-cells", &cell_count); + if (err) { + dev_err(dev, "failed to read #mbox-cells in '%pOF'\n", np); + return err; + } + + if (cell_count == 2) { + max_chans = MHU_CHAN_MAX; + } else { + dev_err(dev, "incorrect value of #mbox-cells in '%pOF'\n", np); + return -EINVAL; + } + + mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL); + if (!mhu) + return -ENOMEM; + + mhu->base = devm_ioremap_resource(dev, &adev->res); + if (IS_ERR(mhu->base)) + return PTR_ERR(mhu->base); + + chans = devm_kcalloc(dev, max_chans, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mhu->dev = dev; + mhu->mbox.dev = dev; + mhu->mbox.chans = chans; + mhu->mbox.num_chans = max_chans; + mhu->mbox.txdone_irq = false; + mhu->mbox.txdone_poll = true; + mhu->mbox.txpoll_period = 1; + + mhu->mbox.of_xlate = mhu_db_mbox_xlate; + amba_set_drvdata(adev, mhu); + + mhu->mbox.ops = &mhu_db_ops; + + err = devm_mbox_controller_register(dev, &mhu->mbox); + if (err) { + dev_err(dev, "Failed to register mailboxes %d\n", err); + return err; + } + + for (i = 0; i < MHU_CHANS; i++) { + int irq = mhu->mlink[i].irq = adev->irq[i]; + + if (irq <= 0) { + dev_dbg(dev, "No IRQ found for Channel %d\n", i); + continue; + } + + mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i]; + mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET; + + err = devm_request_threaded_irq(dev, irq, NULL, + mhu_db_mbox_rx_handler, + IRQF_ONESHOT, "mhu_db_link", mhu); + if (err) { + mbox_controller_unregister(&mhu->mbox); + return err; + } + } + + dev_info(dev, "ARM MHU Doorbell mailbox registered\n"); + return 0; +} + +static const struct amba_id mhu_ids[] = { + { + .id = 0x1bb098, + .mask = 0xffffff, + }, + { 0, 0 }, +}; +MODULE_DEVICE_TABLE(amba, mhu_ids); + +static struct amba_driver arm_mhu_db_driver = { + .drv = { + .name = "mhu-doorbell", + }, + .id_table = mhu_ids, + .probe = mhu_db_probe, +}; +module_amba_driver(arm_mhu_db_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ARM MHU Doorbell Driver"); +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); diff --git a/drivers/mailbox/arm_mhuv2.c b/drivers/mailbox/arm_mhuv2.c new file mode 100644 index 000000000..d4077cd49 --- /dev/null +++ b/drivers/mailbox/arm_mhuv2.c @@ -0,0 +1,1132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Message Handling Unit Version 2 (MHUv2) driver. + * + * Copyright (C) 2020 ARM Ltd. + * Copyright (C) 2020 Linaro Ltd. + * + * An MHUv2 mailbox controller can provide up to 124 channel windows (each 32 + * bit long) and the driver allows any combination of both the transport + * protocol modes: data-transfer and doorbell, to be used on those channel + * windows. + * + * The transport protocols should be specified in the device tree entry for the + * device. The transport protocols determine how the underlying hardware + * resources of the device are utilized when transmitting data. Refer to the + * device tree bindings of the ARM MHUv2 controller for more details. + * + * The number of registered mailbox channels is dependent on both the underlying + * hardware - mainly the number of channel windows implemented by the platform, + * as well as the selected transport protocols. + * + * The MHUv2 controller can work both as a sender and receiver, but the driver + * and the DT bindings support unidirectional transfers for better allocation of + * the channels. That is, this driver will be probed for two separate devices + * for each mailbox controller, a sender device and a receiver device. + */ + +#include <linux/amba/bus.h> +#include <linux/interrupt.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/arm_mhuv2_message.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/spinlock.h> + +/* ====== MHUv2 Registers ====== */ + +/* Maximum number of channel windows */ +#define MHUV2_CH_WN_MAX 124 +/* Number of combined interrupt status registers */ +#define MHUV2_CMB_INT_ST_REG_CNT 4 +#define MHUV2_STAT_BYTES (sizeof(u32)) +#define MHUV2_STAT_BITS (MHUV2_STAT_BYTES * __CHAR_BIT__) + +#define LSB_MASK(n) ((1 << (n * __CHAR_BIT__)) - 1) +#define MHUV2_PROTOCOL_PROP "arm,mhuv2-protocols" + +/* Register Message Handling Unit Configuration fields */ +struct mhu_cfg_t { + u32 num_ch : 7; + u32 pad : 25; +} __packed; + +/* register Interrupt Status fields */ +struct int_st_t { + u32 nr2r : 1; + u32 r2nr : 1; + u32 pad : 30; +} __packed; + +/* Register Interrupt Clear fields */ +struct int_clr_t { + u32 nr2r : 1; + u32 r2nr : 1; + u32 pad : 30; +} __packed; + +/* Register Interrupt Enable fields */ +struct int_en_t { + u32 r2nr : 1; + u32 nr2r : 1; + u32 chcomb : 1; + u32 pad : 29; +} __packed; + +/* Register Implementer Identification fields */ +struct iidr_t { + u32 implementer : 12; + u32 revision : 4; + u32 variant : 4; + u32 product_id : 12; +} __packed; + +/* Register Architecture Identification Register fields */ +struct aidr_t { + u32 arch_minor_rev : 4; + u32 arch_major_rev : 4; + u32 pad : 24; +} __packed; + +/* Sender Channel Window fields */ +struct mhu2_send_ch_wn_reg { + u32 stat; + u8 pad1[0x0C - 0x04]; + u32 stat_set; + u32 int_st; + u32 int_clr; + u32 int_en; + u8 pad2[0x20 - 0x1C]; +} __packed; + +/* Sender frame register fields */ +struct mhu2_send_frame_reg { + struct mhu2_send_ch_wn_reg ch_wn[MHUV2_CH_WN_MAX]; + struct mhu_cfg_t mhu_cfg; + u32 resp_cfg; + u32 access_request; + u32 access_ready; + struct int_st_t int_st; + struct int_clr_t int_clr; + struct int_en_t int_en; + u32 reserved0; + u32 chcomb_int_st[MHUV2_CMB_INT_ST_REG_CNT]; + u8 pad[0xFC8 - 0xFB0]; + struct iidr_t iidr; + struct aidr_t aidr; +} __packed; + +/* Receiver Channel Window fields */ +struct mhu2_recv_ch_wn_reg { + u32 stat; + u32 stat_masked; + u32 stat_clear; + u8 reserved0[0x10 - 0x0C]; + u32 mask; + u32 mask_set; + u32 mask_clear; + u8 pad[0x20 - 0x1C]; +} __packed; + +/* Receiver frame register fields */ +struct mhu2_recv_frame_reg { + struct mhu2_recv_ch_wn_reg ch_wn[MHUV2_CH_WN_MAX]; + struct mhu_cfg_t mhu_cfg; + u8 reserved0[0xF90 - 0xF84]; + struct int_st_t int_st; + struct int_clr_t int_clr; + struct int_en_t int_en; + u32 pad; + u32 chcomb_int_st[MHUV2_CMB_INT_ST_REG_CNT]; + u8 reserved2[0xFC8 - 0xFB0]; + struct iidr_t iidr; + struct aidr_t aidr; +} __packed; + + +/* ====== MHUv2 data structures ====== */ + +enum mhuv2_transport_protocol { + DOORBELL = 0, + DATA_TRANSFER = 1 +}; + +enum mhuv2_frame { + RECEIVER_FRAME, + SENDER_FRAME +}; + +/** + * struct mhuv2 - MHUv2 mailbox controller data + * + * @mbox: Mailbox controller belonging to the MHU frame. + * @send: Base address of the register mapping region. + * @recv: Base address of the register mapping region. + * @frame: Frame type: RECEIVER_FRAME or SENDER_FRAME. + * @irq: Interrupt. + * @windows: Channel windows implemented by the platform. + * @minor: Minor version of the controller. + * @length: Length of the protocols array in bytes. + * @protocols: Raw protocol information, derived from device tree. + * @doorbell_pending_lock: spinlock required for correct operation of Tx + * interrupt for doorbells. + */ +struct mhuv2 { + struct mbox_controller mbox; + union { + struct mhu2_send_frame_reg __iomem *send; + struct mhu2_recv_frame_reg __iomem *recv; + }; + enum mhuv2_frame frame; + unsigned int irq; + unsigned int windows; + unsigned int minor; + unsigned int length; + u32 *protocols; + + spinlock_t doorbell_pending_lock; +}; + +#define mhu_from_mbox(_mbox) container_of(_mbox, struct mhuv2, mbox) + +/** + * struct mhuv2_protocol_ops - MHUv2 operations + * + * Each transport protocol must provide an implementation of the operations + * provided here. + * + * @rx_startup: Startup callback for receiver. + * @rx_shutdown: Shutdown callback for receiver. + * @read_data: Reads and clears newly available data. + * @tx_startup: Startup callback for receiver. + * @tx_shutdown: Shutdown callback for receiver. + * @last_tx_done: Report back if the last tx is completed or not. + * @send_data: Send data to the receiver. + */ +struct mhuv2_protocol_ops { + int (*rx_startup)(struct mhuv2 *mhu, struct mbox_chan *chan); + void (*rx_shutdown)(struct mhuv2 *mhu, struct mbox_chan *chan); + void *(*read_data)(struct mhuv2 *mhu, struct mbox_chan *chan); + + void (*tx_startup)(struct mhuv2 *mhu, struct mbox_chan *chan); + void (*tx_shutdown)(struct mhuv2 *mhu, struct mbox_chan *chan); + int (*last_tx_done)(struct mhuv2 *mhu, struct mbox_chan *chan); + int (*send_data)(struct mhuv2 *mhu, struct mbox_chan *chan, void *arg); +}; + +/* + * MHUv2 mailbox channel's private information + * + * @ops: protocol specific ops for the channel. + * @ch_wn_idx: Channel window index allocated to the channel. + * @windows: Total number of windows consumed by the channel, only relevant + * in DATA_TRANSFER protocol. + * @doorbell: Doorbell bit number within the ch_wn_idx window, only relevant + * in DOORBELL protocol. + * @pending: Flag indicating pending doorbell interrupt, only relevant in + * DOORBELL protocol. + */ +struct mhuv2_mbox_chan_priv { + const struct mhuv2_protocol_ops *ops; + u32 ch_wn_idx; + union { + u32 windows; + struct { + u32 doorbell; + u32 pending; + }; + }; +}; + +/* Macro for reading a bitfield within a physically mapped packed struct */ +#define readl_relaxed_bitfield(_regptr, _type, _field) \ + ({ \ + u32 _regval; \ + _regval = readl_relaxed((_regptr)); \ + (*(_type *)(&_regval))._field; \ + }) + +/* Macro for writing a bitfield within a physically mapped packed struct */ +#define writel_relaxed_bitfield(_value, _regptr, _type, _field) \ + ({ \ + u32 _regval; \ + _regval = readl_relaxed(_regptr); \ + (*(_type *)(&_regval))._field = _value; \ + writel_relaxed(_regval, _regptr); \ + }) + + +/* =================== Doorbell transport protocol operations =============== */ + +static int mhuv2_doorbell_rx_startup(struct mhuv2 *mhu, struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + writel_relaxed(BIT(priv->doorbell), + &mhu->recv->ch_wn[priv->ch_wn_idx].mask_clear); + return 0; +} + +static void mhuv2_doorbell_rx_shutdown(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + writel_relaxed(BIT(priv->doorbell), + &mhu->recv->ch_wn[priv->ch_wn_idx].mask_set); +} + +static void *mhuv2_doorbell_read_data(struct mhuv2 *mhu, struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + writel_relaxed(BIT(priv->doorbell), + &mhu->recv->ch_wn[priv->ch_wn_idx].stat_clear); + return NULL; +} + +static int mhuv2_doorbell_last_tx_done(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + return !(readl_relaxed(&mhu->send->ch_wn[priv->ch_wn_idx].stat) & + BIT(priv->doorbell)); +} + +static int mhuv2_doorbell_send_data(struct mhuv2 *mhu, struct mbox_chan *chan, + void *arg) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + unsigned long flags; + + spin_lock_irqsave(&mhu->doorbell_pending_lock, flags); + + priv->pending = 1; + writel_relaxed(BIT(priv->doorbell), + &mhu->send->ch_wn[priv->ch_wn_idx].stat_set); + + spin_unlock_irqrestore(&mhu->doorbell_pending_lock, flags); + + return 0; +} + +static const struct mhuv2_protocol_ops mhuv2_doorbell_ops = { + .rx_startup = mhuv2_doorbell_rx_startup, + .rx_shutdown = mhuv2_doorbell_rx_shutdown, + .read_data = mhuv2_doorbell_read_data, + .last_tx_done = mhuv2_doorbell_last_tx_done, + .send_data = mhuv2_doorbell_send_data, +}; +#define IS_PROTOCOL_DOORBELL(_priv) (_priv->ops == &mhuv2_doorbell_ops) + +/* ============= Data transfer transport protocol operations ================ */ + +static int mhuv2_data_transfer_rx_startup(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int i = priv->ch_wn_idx + priv->windows - 1; + + /* + * The protocol mandates that all but the last status register must be + * masked. + */ + writel_relaxed(0xFFFFFFFF, &mhu->recv->ch_wn[i].mask_clear); + return 0; +} + +static void mhuv2_data_transfer_rx_shutdown(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int i = priv->ch_wn_idx + priv->windows - 1; + + writel_relaxed(0xFFFFFFFF, &mhu->recv->ch_wn[i].mask_set); +} + +static void *mhuv2_data_transfer_read_data(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + const int windows = priv->windows; + struct arm_mhuv2_mbox_msg *msg; + u32 *data; + int i, idx; + + msg = kzalloc(sizeof(*msg) + windows * MHUV2_STAT_BYTES, GFP_KERNEL); + if (!msg) + return ERR_PTR(-ENOMEM); + + data = msg->data = msg + 1; + msg->len = windows * MHUV2_STAT_BYTES; + + /* + * Messages are expected in order of most significant word to least + * significant word. Refer mhuv2_data_transfer_send_data() for more + * details. + * + * We also need to read the stat register instead of stat_masked, as we + * masked all but the last window. + * + * Last channel window must be cleared as the final operation. Upon + * clearing the last channel window register, which is unmasked in + * data-transfer protocol, the interrupt is de-asserted. + */ + for (i = 0; i < windows; i++) { + idx = priv->ch_wn_idx + i; + data[windows - 1 - i] = readl_relaxed(&mhu->recv->ch_wn[idx].stat); + writel_relaxed(0xFFFFFFFF, &mhu->recv->ch_wn[idx].stat_clear); + } + + return msg; +} + +static void mhuv2_data_transfer_tx_startup(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int i = priv->ch_wn_idx + priv->windows - 1; + + /* Enable interrupts only for the last window */ + if (mhu->minor) { + writel_relaxed(0x1, &mhu->send->ch_wn[i].int_clr); + writel_relaxed(0x1, &mhu->send->ch_wn[i].int_en); + } +} + +static void mhuv2_data_transfer_tx_shutdown(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int i = priv->ch_wn_idx + priv->windows - 1; + + if (mhu->minor) + writel_relaxed(0x0, &mhu->send->ch_wn[i].int_en); +} + +static int mhuv2_data_transfer_last_tx_done(struct mhuv2 *mhu, + struct mbox_chan *chan) +{ + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int i = priv->ch_wn_idx + priv->windows - 1; + + /* Just checking the last channel window should be enough */ + return !readl_relaxed(&mhu->send->ch_wn[i].stat); +} + +/* + * Message will be transmitted from most significant to least significant word. + * This is to allow for messages shorter than channel windows to still trigger + * the receiver interrupt which gets activated when the last stat register is + * written. As an example, a 6-word message is to be written on a 4-channel MHU + * connection: Registers marked with '*' are masked, and will not generate an + * interrupt on the receiver side once written. + * + * u32 *data = [0x00000001], [0x00000002], [0x00000003], [0x00000004], + * [0x00000005], [0x00000006] + * + * ROUND 1: + * stat reg To write Write sequence + * [ stat 3 ] <- [0x00000001] 4 <- triggers interrupt on receiver + * [ stat 2 ] <- [0x00000002] 3 + * [ stat 1 ] <- [0x00000003] 2 + * [ stat 0 ] <- [0x00000004] 1 + * + * data += 4 // Increment data pointer by number of stat regs + * + * ROUND 2: + * stat reg To write Write sequence + * [ stat 3 ] <- [0x00000005] 2 <- triggers interrupt on receiver + * [ stat 2 ] <- [0x00000006] 1 + * [ stat 1 ] <- [0x00000000] + * [ stat 0 ] <- [0x00000000] + */ +static int mhuv2_data_transfer_send_data(struct mhuv2 *mhu, + struct mbox_chan *chan, void *arg) +{ + const struct arm_mhuv2_mbox_msg *msg = arg; + int bytes_left = msg->len, bytes_to_send, bytes_in_round, i; + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + int windows = priv->windows; + u32 *data = msg->data, word; + + while (bytes_left) { + if (!data[0]) { + dev_err(mhu->mbox.dev, "Data aligned at first window can't be zero to guarantee interrupt generation at receiver"); + return -EINVAL; + } + + while(!mhuv2_data_transfer_last_tx_done(mhu, chan)) + continue; + + bytes_in_round = min(bytes_left, (int)(windows * MHUV2_STAT_BYTES)); + + for (i = windows - 1; i >= 0; i--) { + /* Data less than windows can transfer ? */ + if (unlikely(bytes_in_round <= i * MHUV2_STAT_BYTES)) + continue; + + word = data[i]; + bytes_to_send = bytes_in_round & (MHUV2_STAT_BYTES - 1); + if (unlikely(bytes_to_send)) + word &= LSB_MASK(bytes_to_send); + else + bytes_to_send = MHUV2_STAT_BYTES; + + writel_relaxed(word, &mhu->send->ch_wn[priv->ch_wn_idx + windows - 1 - i].stat_set); + bytes_left -= bytes_to_send; + bytes_in_round -= bytes_to_send; + } + + data += windows; + } + + return 0; +} + +static const struct mhuv2_protocol_ops mhuv2_data_transfer_ops = { + .rx_startup = mhuv2_data_transfer_rx_startup, + .rx_shutdown = mhuv2_data_transfer_rx_shutdown, + .read_data = mhuv2_data_transfer_read_data, + .tx_startup = mhuv2_data_transfer_tx_startup, + .tx_shutdown = mhuv2_data_transfer_tx_shutdown, + .last_tx_done = mhuv2_data_transfer_last_tx_done, + .send_data = mhuv2_data_transfer_send_data, +}; + +/* Interrupt handlers */ + +static struct mbox_chan *get_irq_chan_comb(struct mhuv2 *mhu, u32 __iomem *reg) +{ + struct mbox_chan *chans = mhu->mbox.chans; + int channel = 0, i, j, offset = 0, windows, protocol, ch_wn; + u32 stat; + + for (i = 0; i < MHUV2_CMB_INT_ST_REG_CNT; i++) { + stat = readl_relaxed(reg + i); + if (!stat) + continue; + + ch_wn = i * MHUV2_STAT_BITS + __builtin_ctz(stat); + + for (j = 0; j < mhu->length; j += 2) { + protocol = mhu->protocols[j]; + windows = mhu->protocols[j + 1]; + + if (ch_wn >= offset + windows) { + if (protocol == DOORBELL) + channel += MHUV2_STAT_BITS * windows; + else + channel++; + + offset += windows; + continue; + } + + /* Return first chan of the window in doorbell mode */ + if (protocol == DOORBELL) + channel += MHUV2_STAT_BITS * (ch_wn - offset); + + return &chans[channel]; + } + } + + return ERR_PTR(-EIO); +} + +static irqreturn_t mhuv2_sender_interrupt(int irq, void *data) +{ + struct mhuv2 *mhu = data; + struct device *dev = mhu->mbox.dev; + struct mhuv2_mbox_chan_priv *priv; + struct mbox_chan *chan; + unsigned long flags; + int i, found = 0; + u32 stat; + + chan = get_irq_chan_comb(mhu, mhu->send->chcomb_int_st); + if (IS_ERR(chan)) { + dev_warn(dev, "Failed to find channel for the Tx interrupt\n"); + return IRQ_NONE; + } + priv = chan->con_priv; + + if (!IS_PROTOCOL_DOORBELL(priv)) { + for (i = 0; i < priv->windows; i++) + writel_relaxed(1, &mhu->send->ch_wn[priv->ch_wn_idx + i].int_clr); + + if (chan->cl) { + mbox_chan_txdone(chan, 0); + return IRQ_HANDLED; + } + + dev_warn(dev, "Tx interrupt Received on channel (%u) not currently attached to a mailbox client\n", + priv->ch_wn_idx); + return IRQ_NONE; + } + + /* Clear the interrupt first, so we don't miss any doorbell later */ + writel_relaxed(1, &mhu->send->ch_wn[priv->ch_wn_idx].int_clr); + + /* + * In Doorbell mode, make sure no new transitions happen while the + * interrupt handler is trying to find the finished doorbell tx + * operations, else we may think few of the transfers were complete + * before they actually were. + */ + spin_lock_irqsave(&mhu->doorbell_pending_lock, flags); + + /* + * In case of doorbell mode, the first channel of the window is returned + * by get_irq_chan_comb(). Find all the pending channels here. + */ + stat = readl_relaxed(&mhu->send->ch_wn[priv->ch_wn_idx].stat); + + for (i = 0; i < MHUV2_STAT_BITS; i++) { + priv = chan[i].con_priv; + + /* Find cases where pending was 1, but stat's bit is cleared */ + if (priv->pending ^ ((stat >> i) & 0x1)) { + BUG_ON(!priv->pending); + + if (!chan->cl) { + dev_warn(dev, "Tx interrupt received on doorbell (%u : %u) channel not currently attached to a mailbox client\n", + priv->ch_wn_idx, i); + continue; + } + + mbox_chan_txdone(&chan[i], 0); + priv->pending = 0; + found++; + } + } + + spin_unlock_irqrestore(&mhu->doorbell_pending_lock, flags); + + if (!found) { + /* + * We may have already processed the doorbell in the previous + * iteration if the interrupt came right after we cleared it but + * before we read the stat register. + */ + dev_dbg(dev, "Couldn't find the doorbell (%u) for the Tx interrupt interrupt\n", + priv->ch_wn_idx); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static struct mbox_chan *get_irq_chan_comb_rx(struct mhuv2 *mhu) +{ + struct mhuv2_mbox_chan_priv *priv; + struct mbox_chan *chan; + u32 stat; + + chan = get_irq_chan_comb(mhu, mhu->recv->chcomb_int_st); + if (IS_ERR(chan)) + return chan; + + priv = chan->con_priv; + if (!IS_PROTOCOL_DOORBELL(priv)) + return chan; + + /* + * In case of doorbell mode, the first channel of the window is returned + * by the routine. Find the exact channel here. + */ + stat = readl_relaxed(&mhu->recv->ch_wn[priv->ch_wn_idx].stat_masked); + BUG_ON(!stat); + + return chan + __builtin_ctz(stat); +} + +static struct mbox_chan *get_irq_chan_stat_rx(struct mhuv2 *mhu) +{ + struct mbox_chan *chans = mhu->mbox.chans; + struct mhuv2_mbox_chan_priv *priv; + u32 stat; + int i = 0; + + while (i < mhu->mbox.num_chans) { + priv = chans[i].con_priv; + stat = readl_relaxed(&mhu->recv->ch_wn[priv->ch_wn_idx].stat_masked); + + if (stat) { + if (IS_PROTOCOL_DOORBELL(priv)) + i += __builtin_ctz(stat); + return &chans[i]; + } + + i += IS_PROTOCOL_DOORBELL(priv) ? MHUV2_STAT_BITS : 1; + } + + return ERR_PTR(-EIO); +} + +static struct mbox_chan *get_irq_chan_rx(struct mhuv2 *mhu) +{ + if (!mhu->minor) + return get_irq_chan_stat_rx(mhu); + + return get_irq_chan_comb_rx(mhu); +} + +static irqreturn_t mhuv2_receiver_interrupt(int irq, void *arg) +{ + struct mhuv2 *mhu = arg; + struct mbox_chan *chan = get_irq_chan_rx(mhu); + struct device *dev = mhu->mbox.dev; + struct mhuv2_mbox_chan_priv *priv; + int ret = IRQ_NONE; + void *data; + + if (IS_ERR(chan)) { + dev_warn(dev, "Failed to find channel for the rx interrupt\n"); + return IRQ_NONE; + } + priv = chan->con_priv; + + /* Read and clear the data first */ + data = priv->ops->read_data(mhu, chan); + + if (!chan->cl) { + dev_warn(dev, "Received data on channel (%u) not currently attached to a mailbox client\n", + priv->ch_wn_idx); + } else if (IS_ERR(data)) { + dev_err(dev, "Failed to read data: %lu\n", PTR_ERR(data)); + } else { + mbox_chan_received_data(chan, data); + ret = IRQ_HANDLED; + } + + if (!IS_ERR(data)) + kfree(data); + + return ret; +} + +/* Sender and receiver ops */ +static bool mhuv2_sender_last_tx_done(struct mbox_chan *chan) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + return priv->ops->last_tx_done(mhu, chan); +} + +static int mhuv2_sender_send_data(struct mbox_chan *chan, void *data) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + if (!priv->ops->last_tx_done(mhu, chan)) + return -EBUSY; + + return priv->ops->send_data(mhu, chan, data); +} + +static int mhuv2_sender_startup(struct mbox_chan *chan) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + if (priv->ops->tx_startup) + priv->ops->tx_startup(mhu, chan); + return 0; +} + +static void mhuv2_sender_shutdown(struct mbox_chan *chan) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + if (priv->ops->tx_shutdown) + priv->ops->tx_shutdown(mhu, chan); +} + +static const struct mbox_chan_ops mhuv2_sender_ops = { + .send_data = mhuv2_sender_send_data, + .startup = mhuv2_sender_startup, + .shutdown = mhuv2_sender_shutdown, + .last_tx_done = mhuv2_sender_last_tx_done, +}; + +static int mhuv2_receiver_startup(struct mbox_chan *chan) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + return priv->ops->rx_startup(mhu, chan); +} + +static void mhuv2_receiver_shutdown(struct mbox_chan *chan) +{ + struct mhuv2 *mhu = mhu_from_mbox(chan->mbox); + struct mhuv2_mbox_chan_priv *priv = chan->con_priv; + + priv->ops->rx_shutdown(mhu, chan); +} + +static int mhuv2_receiver_send_data(struct mbox_chan *chan, void *data) +{ + dev_err(chan->mbox->dev, + "Trying to transmit on a receiver MHU frame\n"); + return -EIO; +} + +static bool mhuv2_receiver_last_tx_done(struct mbox_chan *chan) +{ + dev_err(chan->mbox->dev, "Trying to Tx poll on a receiver MHU frame\n"); + return true; +} + +static const struct mbox_chan_ops mhuv2_receiver_ops = { + .send_data = mhuv2_receiver_send_data, + .startup = mhuv2_receiver_startup, + .shutdown = mhuv2_receiver_shutdown, + .last_tx_done = mhuv2_receiver_last_tx_done, +}; + +static struct mbox_chan *mhuv2_mbox_fw_xlate(struct mbox_controller *mbox, + const struct fwnode_reference_args *pa) +{ + struct mhuv2 *mhu = mhu_from_mbox(mbox); + struct mbox_chan *chans = mbox->chans; + int channel = 0, i, offset, doorbell, protocol, windows; + + if (pa->nargs != 2) + return ERR_PTR(-EINVAL); + + offset = pa->args[0]; + doorbell = pa->args[1]; + if (doorbell >= MHUV2_STAT_BITS) + goto out; + + for (i = 0; i < mhu->length; i += 2) { + protocol = mhu->protocols[i]; + windows = mhu->protocols[i + 1]; + + if (protocol == DOORBELL) { + if (offset < windows) + return &chans[channel + MHUV2_STAT_BITS * offset + doorbell]; + + channel += MHUV2_STAT_BITS * windows; + offset -= windows; + } else { + if (offset == 0) { + if (doorbell) + goto out; + + return &chans[channel]; + } + + channel++; + offset--; + } + } + +out: + dev_err(mbox->dev, "Couldn't xlate to a valid channel (%llu: %d)\n", + pa->args[0], doorbell); + return ERR_PTR(-ENODEV); +} + +static int mhuv2_verify_protocol(struct mhuv2 *mhu) +{ + struct device *dev = mhu->mbox.dev; + int protocol, windows, channels = 0, total_windows = 0, i; + + for (i = 0; i < mhu->length; i += 2) { + protocol = mhu->protocols[i]; + windows = mhu->protocols[i + 1]; + + if (!windows) { + dev_err(dev, "Window size can't be zero (%d)\n", i); + return -EINVAL; + } + total_windows += windows; + + if (protocol == DOORBELL) { + channels += MHUV2_STAT_BITS * windows; + } else if (protocol == DATA_TRANSFER) { + channels++; + } else { + dev_err(dev, "Invalid protocol (%d) present in %s property at index %d\n", + protocol, MHUV2_PROTOCOL_PROP, i); + return -EINVAL; + } + } + + if (total_windows > mhu->windows) { + dev_err(dev, "Channel windows can't be more than what's implemented by the hardware ( %d: %d)\n", + total_windows, mhu->windows); + return -EINVAL; + } + + mhu->mbox.num_chans = channels; + return 0; +} + +static int mhuv2_allocate_channels(struct mhuv2 *mhu) +{ + struct mbox_controller *mbox = &mhu->mbox; + struct mhuv2_mbox_chan_priv *priv; + struct device *dev = mbox->dev; + struct mbox_chan *chans; + int protocol, windows = 0, next_window = 0, i, j, k; + + chans = devm_kcalloc(dev, mbox->num_chans, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mbox->chans = chans; + + for (i = 0; i < mhu->length; i += 2) { + next_window += windows; + + protocol = mhu->protocols[i]; + windows = mhu->protocols[i + 1]; + + if (protocol == DATA_TRANSFER) { + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ch_wn_idx = next_window; + priv->ops = &mhuv2_data_transfer_ops; + priv->windows = windows; + chans++->con_priv = priv; + continue; + } + + for (j = 0; j < windows; j++) { + for (k = 0; k < MHUV2_STAT_BITS; k++) { + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ch_wn_idx = next_window + j; + priv->ops = &mhuv2_doorbell_ops; + priv->doorbell = k; + chans++->con_priv = priv; + } + + /* + * Permanently enable interrupt as we can't + * control it per doorbell. + */ + if (mhu->frame == SENDER_FRAME && mhu->minor) + writel_relaxed(0x1, &mhu->send->ch_wn[priv->ch_wn_idx].int_en); + } + } + + /* Make sure we have initialized all channels */ + BUG_ON(chans - mbox->chans != mbox->num_chans); + + return 0; +} + +static int mhuv2_parse_channels(struct mhuv2 *mhu) +{ + struct device *dev = mhu->mbox.dev; + int ret, count; + u32 *protocols; + + count = device_property_count_u32(dev, MHUV2_PROTOCOL_PROP); + if (count <= 0 || count % 2) { + dev_err(dev, "Invalid %s property (%d)\n", MHUV2_PROTOCOL_PROP, + count); + return -EINVAL; + } + + protocols = devm_kmalloc_array(dev, count, sizeof(*protocols), GFP_KERNEL); + if (!protocols) + return -ENOMEM; + + ret = device_property_read_u32_array(dev, MHUV2_PROTOCOL_PROP, protocols, count); + if (ret) { + dev_err(dev, "Failed to read %s property: %d\n", + MHUV2_PROTOCOL_PROP, ret); + return ret; + } + + mhu->protocols = protocols; + mhu->length = count; + + ret = mhuv2_verify_protocol(mhu); + if (ret) + return ret; + + return mhuv2_allocate_channels(mhu); +} + +static int mhuv2_tx_init(struct amba_device *adev, struct mhuv2 *mhu, + void __iomem *reg) +{ + struct device *dev = mhu->mbox.dev; + int ret, i; + + mhu->frame = SENDER_FRAME; + mhu->mbox.ops = &mhuv2_sender_ops; + mhu->send = reg; + + mhu->windows = readl_relaxed_bitfield(&mhu->send->mhu_cfg, struct mhu_cfg_t, num_ch); + mhu->minor = readl_relaxed_bitfield(&mhu->send->aidr, struct aidr_t, arch_minor_rev); + + spin_lock_init(&mhu->doorbell_pending_lock); + + /* + * For minor version 1 and forward, tx interrupt is provided by + * the controller. + */ + if (mhu->minor && adev->irq[0]) { + ret = devm_request_threaded_irq(dev, adev->irq[0], NULL, + mhuv2_sender_interrupt, + IRQF_ONESHOT, "mhuv2-tx", mhu); + if (!ret) { + mhu->mbox.txdone_irq = true; + mhu->mbox.txdone_poll = false; + mhu->irq = adev->irq[0]; + + writel_relaxed_bitfield(1, &mhu->send->int_en, struct int_en_t, chcomb); + + /* Disable all channel interrupts */ + for (i = 0; i < mhu->windows; i++) + writel_relaxed(0x0, &mhu->send->ch_wn[i].int_en); + + goto out; + } + } + + mhu->mbox.txdone_irq = false; + mhu->mbox.txdone_poll = true; + mhu->mbox.txpoll_period = 1; + +out: + /* Wait for receiver to be ready */ + writel_relaxed(0x1, &mhu->send->access_request); + while (!readl_relaxed(&mhu->send->access_ready)) + continue; + + return 0; +} + +static int mhuv2_rx_init(struct amba_device *adev, struct mhuv2 *mhu, + void __iomem *reg) +{ + struct device *dev = mhu->mbox.dev; + int ret, i; + + mhu->frame = RECEIVER_FRAME; + mhu->mbox.ops = &mhuv2_receiver_ops; + mhu->recv = reg; + + mhu->windows = readl_relaxed_bitfield(&mhu->recv->mhu_cfg, struct mhu_cfg_t, num_ch); + mhu->minor = readl_relaxed_bitfield(&mhu->recv->aidr, struct aidr_t, arch_minor_rev); + + mhu->irq = adev->irq[0]; + if (!mhu->irq) { + dev_err(dev, "Missing receiver IRQ\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, mhu->irq, NULL, + mhuv2_receiver_interrupt, IRQF_ONESHOT, + "mhuv2-rx", mhu); + if (ret) + return ret; + + /* Mask all the channel windows */ + for (i = 0; i < mhu->windows; i++) + writel_relaxed(0xFFFFFFFF, &mhu->recv->ch_wn[i].mask_set); + + if (mhu->minor) + writel_relaxed_bitfield(1, &mhu->recv->int_en, struct int_en_t, chcomb); + + return 0; +} + +static int mhuv2_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct device *dev = &adev->dev; + const struct device_node *np = dev->of_node; + struct mhuv2 *mhu; + void __iomem *reg; + int ret = -EINVAL; + + reg = devm_of_iomap(dev, dev->of_node, 0, NULL); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL); + if (!mhu) + return -ENOMEM; + + mhu->mbox.dev = dev; + mhu->mbox.fw_xlate = mhuv2_mbox_fw_xlate; + + if (of_device_is_compatible(np, "arm,mhuv2-tx")) + ret = mhuv2_tx_init(adev, mhu, reg); + else if (of_device_is_compatible(np, "arm,mhuv2-rx")) + ret = mhuv2_rx_init(adev, mhu, reg); + else + dev_err(dev, "Invalid compatible property\n"); + + if (ret) + return ret; + + /* Channel windows can't be 0 */ + BUG_ON(!mhu->windows); + + ret = mhuv2_parse_channels(mhu); + if (ret) + return ret; + + amba_set_drvdata(adev, mhu); + + ret = devm_mbox_controller_register(dev, &mhu->mbox); + if (ret) + dev_err(dev, "failed to register ARM MHUv2 driver %d\n", ret); + + return ret; +} + +static void mhuv2_remove(struct amba_device *adev) +{ + struct mhuv2 *mhu = amba_get_drvdata(adev); + + if (mhu->frame == SENDER_FRAME) + writel_relaxed(0x0, &mhu->send->access_request); +} + +static const struct amba_id mhuv2_ids[] = { + { + /* 2.0 */ + .id = 0xbb0d1, + .mask = 0xfffff, + }, + { + /* 2.1 */ + .id = 0xbb076, + .mask = 0xfffff, + }, + { 0, 0 }, +}; +MODULE_DEVICE_TABLE(amba, mhuv2_ids); + +static struct amba_driver mhuv2_driver = { + .drv = { + .name = "arm-mhuv2", + }, + .id_table = mhuv2_ids, + .probe = mhuv2_probe, + .remove = mhuv2_remove, +}; +module_amba_driver(mhuv2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ARM MHUv2 Driver"); +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>"); +MODULE_AUTHOR("Tushar Khandelwal <tushar.khandelwal@arm.com>"); diff --git a/drivers/mailbox/arm_mhuv3.c b/drivers/mailbox/arm_mhuv3.c new file mode 100644 index 000000000..6569c3dda --- /dev/null +++ b/drivers/mailbox/arm_mhuv3.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Message Handling Unit Version 3 (MHUv3) driver. + * + * Copyright (C) 2024 ARM Ltd. + * + * Based on ARM MHUv2 driver. + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* ====== MHUv3 Registers ====== */ + +/* Maximum number of Doorbell channel windows */ +#define MHUV3_DBCW_MAX 128 +/* Number of DBCH combined interrupt status registers */ +#define MHUV3_DBCH_CMB_INT_ST_REG_CNT 4 + +/* Number of FFCH combined interrupt status registers */ +#define MHUV3_FFCH_CMB_INT_ST_REG_CNT 2 + +#define MHUV3_FLAG_BITS 32 + +/* Not a typo ... */ +#define MHUV3_MAJOR_VERSION 2 + +enum { + MHUV3_MBOX_CELL_TYPE, + MHUV3_MBOX_CELL_CHWN, + MHUV3_MBOX_CELL_PARAM, + MHUV3_MBOX_CELLS +}; + +/* Padding bitfields/fields represents hole in the regs MMIO */ + +/* CTRL_Page */ +struct blk_id { +#define id GENMASK(3, 0) + u32 val; +} __packed; + +struct feat_spt0 { +#define dbe_spt GENMASK(3, 0) +#define fe_spt GENMASK(7, 4) +#define fce_spt GENMASK(11, 8) + u32 val; +} __packed; + +struct feat_spt1 { +#define auto_op_spt GENMASK(3, 0) + u32 val; +} __packed; + +struct dbch_cfg0 { +#define num_dbch GENMASK(7, 0) + u32 val; +} __packed; + +struct ffch_cfg0 { +#define num_ffch GENMASK(7, 0) +#define x8ba_spt BIT(8) +#define x16ba_spt BIT(9) +#define x32ba_spt BIT(10) +#define x64ba_spt BIT(11) +#define ffch_depth GENMASK(25, 16) + u32 val; +} __packed; + +struct fch_cfg0 { +#define num_fch GENMASK(9, 0) +#define fcgi_spt BIT(10) // MBX-only +#define num_fcg GENMASK(15, 11) +#define num_fch_per_grp GENMASK(20, 16) +#define fch_ws GENMASK(28, 21) + u32 val; +} __packed; + +struct ctrl { +#define op_req BIT(0) +#define ch_op_mask BIT(1) + u32 val; +} __packed; + +struct fch_ctrl { +#define _int_en BIT(2) + u32 val; +} __packed; + +struct iidr { +#define implementer GENMASK(11, 0) +#define revision GENMASK(15, 12) +#define variant GENMASK(19, 16) +#define product_id GENMASK(31, 20) + u32 val; +} __packed; + +struct aidr { +#define arch_minor_rev GENMASK(3, 0) +#define arch_major_rev GENMASK(7, 4) + u32 val; +} __packed; + +struct ctrl_page { + struct blk_id blk_id; + u8 pad[12]; + struct feat_spt0 feat_spt0; + struct feat_spt1 feat_spt1; + u8 pad1[8]; + struct dbch_cfg0 dbch_cfg0; + u8 pad2[12]; + struct ffch_cfg0 ffch_cfg0; + u8 pad3[12]; + struct fch_cfg0 fch_cfg0; + u8 pad4[188]; + struct ctrl x_ctrl; + /*-- MBX-only registers --*/ + u8 pad5[60]; + struct fch_ctrl fch_ctrl; + u32 fcg_int_en; + u8 pad6[696]; + /*-- End of MBX-only ---- */ + u32 dbch_int_st[MHUV3_DBCH_CMB_INT_ST_REG_CNT]; + u32 ffch_int_st[MHUV3_FFCH_CMB_INT_ST_REG_CNT]; + /*-- MBX-only registers --*/ + u8 pad7[88]; + u32 fcg_int_st; + u8 pad8[12]; + u32 fcg_grp_int_st[32]; + u8 pad9[2760]; + /*-- End of MBX-only ---- */ + struct iidr iidr; + struct aidr aidr; + u32 imp_def_id[12]; +} __packed; + +/* DBCW_Page */ + +struct xbcw_ctrl { +#define comb_en BIT(0) + u32 val; +} __packed; + +struct pdbcw_int { +#define tfr_ack BIT(0) + u32 val; +} __packed; + +struct pdbcw_page { + u32 st; + u8 pad[8]; + u32 set; + struct pdbcw_int int_st; + struct pdbcw_int int_clr; + struct pdbcw_int int_en; + struct xbcw_ctrl ctrl; +} __packed; + +struct mdbcw_page { + u32 st; + u32 st_msk; + u32 clr; + u8 pad[4]; + u32 msk_st; + u32 msk_set; + u32 msk_clr; + struct xbcw_ctrl ctrl; +} __packed; + +struct dummy_page { + u8 pad[SZ_4K]; +} __packed; + +struct mhu3_pbx_frame_reg { + struct ctrl_page ctrl; + struct pdbcw_page dbcw[MHUV3_DBCW_MAX]; + struct dummy_page ffcw; + struct dummy_page fcw; + u8 pad[SZ_4K * 11]; + struct dummy_page impdef; +} __packed; + +struct mhu3_mbx_frame_reg { + struct ctrl_page ctrl; + struct mdbcw_page dbcw[MHUV3_DBCW_MAX]; + struct dummy_page ffcw; + struct dummy_page fcw; + u8 pad[SZ_4K * 11]; + struct dummy_page impdef; +} __packed; + +/* Macro for reading a bitmask within a physically mapped packed struct */ +#define readl_relaxed_bitmask(_regptr, _bitmask) \ + ({ \ + unsigned long _rval; \ + _rval = readl_relaxed(_regptr); \ + FIELD_GET(_bitmask, _rval); \ + }) + +/* Macro for writing a bitmask within a physically mapped packed struct */ +#define writel_relaxed_bitmask(_value, _regptr, _bitmask) \ + ({ \ + unsigned long _rval; \ + typeof(_regptr) _rptr = _regptr; \ + typeof(_bitmask) _bmask = _bitmask; \ + _rval = readl_relaxed(_rptr); \ + _rval &= ~(_bmask); \ + _rval |= FIELD_PREP((unsigned long long)_bmask, _value);\ + writel_relaxed(_rval, _rptr); \ + }) + +/* ====== MHUv3 data structures ====== */ + +enum mhuv3_frame { + PBX_FRAME, + MBX_FRAME, +}; + +static char *mhuv3_str[] = { + "PBX", + "MBX" +}; + +enum mhuv3_extension_type { + DBE_EXT, + FCE_EXT, + FE_EXT, + NUM_EXT +}; + +static char *mhuv3_ext_str[] = { + "DBE", + "FCE", + "FE" +}; + +struct mhuv3; + +/** + * struct mhuv3_protocol_ops - MHUv3 operations + * + * @rx_startup: Receiver startup callback. + * @rx_shutdown: Receiver shutdown callback. + * @read_data: Read available Sender in-band LE data (if any). + * @rx_complete: Acknowledge data reception to the Sender. Any out-of-band data + * has to have been already retrieved before calling this. + * @tx_startup: Sender startup callback. + * @tx_shutdown: Sender shutdown callback. + * @last_tx_done: Report back to the Sender if the last transfer has completed. + * @send_data: Send data to the receiver. + * + * Each supported transport protocol provides its own implementation of + * these operations. + */ +struct mhuv3_protocol_ops { + int (*rx_startup)(struct mhuv3 *mhu, struct mbox_chan *chan); + void (*rx_shutdown)(struct mhuv3 *mhu, struct mbox_chan *chan); + void *(*read_data)(struct mhuv3 *mhu, struct mbox_chan *chan); + void (*rx_complete)(struct mhuv3 *mhu, struct mbox_chan *chan); + void (*tx_startup)(struct mhuv3 *mhu, struct mbox_chan *chan); + void (*tx_shutdown)(struct mhuv3 *mhu, struct mbox_chan *chan); + int (*last_tx_done)(struct mhuv3 *mhu, struct mbox_chan *chan); + int (*send_data)(struct mhuv3 *mhu, struct mbox_chan *chan, void *arg); +}; + +/** + * struct mhuv3_mbox_chan_priv - MHUv3 channel private information + * + * @ch_idx: Channel window index associated to this mailbox channel. + * @doorbell: Doorbell bit number within the @ch_idx window. + * Only relevant to Doorbell transport. + * @ops: Transport protocol specific operations for this channel. + * + * Transport specific data attached to mmailbox channel priv data. + */ +struct mhuv3_mbox_chan_priv { + u32 ch_idx; + u32 doorbell; + const struct mhuv3_protocol_ops *ops; +}; + +/** + * struct mhuv3_extension - MHUv3 extension descriptor + * + * @type: Type of extension + * @num_chans: Max number of channels found for this extension. + * @base_ch_idx: First channel number assigned to this extension, picked from + * the set of all mailbox channels descriptors created. + * @mbox_of_xlate: Extension specific helper to parse DT and lookup associated + * channel from the related 'mboxes' property. + * @combined_irq_setup: Extension specific helper to setup the combined irq. + * @channels_init: Extension specific helper to initialize channels. + * @chan_from_comb_irq_get: Extension specific helper to lookup which channel + * triggered the combined irq. + * @pending_db: Array of per-channel pending doorbells. + * @pending_lock: Protect access to pending_db. + */ +struct mhuv3_extension { + enum mhuv3_extension_type type; + unsigned int num_chans; + unsigned int base_ch_idx; + struct mbox_chan *(*mbox_of_xlate)(struct mhuv3 *mhu, + unsigned int channel, + unsigned int param); + void (*combined_irq_setup)(struct mhuv3 *mhu); + int (*channels_init)(struct mhuv3 *mhu); + struct mbox_chan *(*chan_from_comb_irq_get)(struct mhuv3 *mhu); + u32 pending_db[MHUV3_DBCW_MAX]; + /* Protect access to pending_db */ + spinlock_t pending_lock; +}; + +/** + * struct mhuv3 - MHUv3 mailbox controller data + * + * @frame: Frame type: MBX_FRAME or PBX_FRAME. + * @auto_op_full: Flag to indicate if the MHU supports AutoOp full mode. + * @major: MHUv3 controller architectural major version. + * @minor: MHUv3 controller architectural minor version. + * @implem: MHUv3 controller IIDR implementer. + * @rev: MHUv3 controller IIDR revision. + * @var: MHUv3 controller IIDR variant. + * @prod_id: MHUv3 controller IIDR product_id. + * @num_chans: The total number of channels discovered across all extensions. + * @cmb_irq: Combined IRQ number if any found defined. + * @ctrl: A reference to the MHUv3 control page for this block. + * @pbx: Base address of the PBX register mapping region. + * @mbx: Base address of the MBX register mapping region. + * @ext: Array holding descriptors for any found implemented extension. + * @mbox: Mailbox controller belonging to the MHU frame. + */ +struct mhuv3 { + enum mhuv3_frame frame; + bool auto_op_full; + unsigned int major; + unsigned int minor; + unsigned int implem; + unsigned int rev; + unsigned int var; + unsigned int prod_id; + unsigned int num_chans; + int cmb_irq; + struct ctrl_page __iomem *ctrl; + union { + struct mhu3_pbx_frame_reg __iomem *pbx; + struct mhu3_mbx_frame_reg __iomem *mbx; + }; + struct mhuv3_extension *ext[NUM_EXT]; + struct mbox_controller mbox; +}; + +#define mhu_from_mbox(_mbox) container_of(_mbox, struct mhuv3, mbox) + +typedef int (*mhuv3_extension_initializer)(struct mhuv3 *mhu); + +/* =================== Doorbell transport protocol operations =============== */ + +static void mhuv3_doorbell_tx_startup(struct mhuv3 *mhu, struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + + /* Enable Transfer Acknowledgment events */ + writel_relaxed_bitmask(0x1, &mhu->pbx->dbcw[priv->ch_idx].int_en, tfr_ack); +} + +static void mhuv3_doorbell_tx_shutdown(struct mhuv3 *mhu, struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + unsigned long flags; + + /* Disable Channel Transfer Ack events */ + writel_relaxed_bitmask(0x0, &mhu->pbx->dbcw[priv->ch_idx].int_en, tfr_ack); + + /* Clear Channel Transfer Ack and pending doorbells */ + writel_relaxed_bitmask(0x1, &mhu->pbx->dbcw[priv->ch_idx].int_clr, tfr_ack); + spin_lock_irqsave(&e->pending_lock, flags); + e->pending_db[priv->ch_idx] = 0; + spin_unlock_irqrestore(&e->pending_lock, flags); +} + +static int mhuv3_doorbell_rx_startup(struct mhuv3 *mhu, struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + + /* Unmask Channel Transfer events */ + writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].msk_clr); + + return 0; +} + +static void mhuv3_doorbell_rx_shutdown(struct mhuv3 *mhu, + struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + + /* Mask Channel Transfer events */ + writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].msk_set); +} + +static void mhuv3_doorbell_rx_complete(struct mhuv3 *mhu, struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + + /* Clearing the pending transfer generates the Channel Transfer Ack */ + writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].clr); +} + +static int mhuv3_doorbell_last_tx_done(struct mhuv3 *mhu, + struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + int done; + + done = !(readl_relaxed(&mhu->pbx->dbcw[priv->ch_idx].st) & + BIT(priv->doorbell)); + if (done) { + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + unsigned long flags; + + /* Take care to clear the pending doorbell also when polling */ + spin_lock_irqsave(&e->pending_lock, flags); + e->pending_db[priv->ch_idx] &= ~BIT(priv->doorbell); + spin_unlock_irqrestore(&e->pending_lock, flags); + } + + return done; +} + +static int mhuv3_doorbell_send_data(struct mhuv3 *mhu, struct mbox_chan *chan, + void *arg) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + + scoped_guard(spinlock_irqsave, &e->pending_lock) { + /* Only one in-flight Transfer is allowed per-doorbell */ + if (e->pending_db[priv->ch_idx] & BIT(priv->doorbell)) + return -EBUSY; + + e->pending_db[priv->ch_idx] |= BIT(priv->doorbell); + } + + writel_relaxed(BIT(priv->doorbell), &mhu->pbx->dbcw[priv->ch_idx].set); + + return 0; +} + +static const struct mhuv3_protocol_ops mhuv3_doorbell_ops = { + .tx_startup = mhuv3_doorbell_tx_startup, + .tx_shutdown = mhuv3_doorbell_tx_shutdown, + .rx_startup = mhuv3_doorbell_rx_startup, + .rx_shutdown = mhuv3_doorbell_rx_shutdown, + .rx_complete = mhuv3_doorbell_rx_complete, + .last_tx_done = mhuv3_doorbell_last_tx_done, + .send_data = mhuv3_doorbell_send_data, +}; + +/* Sender and receiver mailbox ops */ +static bool mhuv3_sender_last_tx_done(struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + return priv->ops->last_tx_done(mhu, chan); +} + +static int mhuv3_sender_send_data(struct mbox_chan *chan, void *data) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + if (!priv->ops->last_tx_done(mhu, chan)) + return -EBUSY; + + return priv->ops->send_data(mhu, chan, data); +} + +static int mhuv3_sender_startup(struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + if (priv->ops->tx_startup) + priv->ops->tx_startup(mhu, chan); + + return 0; +} + +static void mhuv3_sender_shutdown(struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + if (priv->ops->tx_shutdown) + priv->ops->tx_shutdown(mhu, chan); +} + +static const struct mbox_chan_ops mhuv3_sender_ops = { + .send_data = mhuv3_sender_send_data, + .startup = mhuv3_sender_startup, + .shutdown = mhuv3_sender_shutdown, + .last_tx_done = mhuv3_sender_last_tx_done, +}; + +static int mhuv3_receiver_startup(struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + return priv->ops->rx_startup(mhu, chan); +} + +static void mhuv3_receiver_shutdown(struct mbox_chan *chan) +{ + struct mhuv3_mbox_chan_priv *priv = chan->con_priv; + struct mhuv3 *mhu = mhu_from_mbox(chan->mbox); + + priv->ops->rx_shutdown(mhu, chan); +} + +static int mhuv3_receiver_send_data(struct mbox_chan *chan, void *data) +{ + dev_err(chan->mbox->dev, + "Trying to transmit on a MBX MHUv3 frame\n"); + return -EIO; +} + +static bool mhuv3_receiver_last_tx_done(struct mbox_chan *chan) +{ + dev_err(chan->mbox->dev, "Trying to Tx poll on a MBX MHUv3 frame\n"); + return true; +} + +static const struct mbox_chan_ops mhuv3_receiver_ops = { + .send_data = mhuv3_receiver_send_data, + .startup = mhuv3_receiver_startup, + .shutdown = mhuv3_receiver_shutdown, + .last_tx_done = mhuv3_receiver_last_tx_done, +}; + +static struct mbox_chan *mhuv3_dbe_mbox_of_xlate(struct mhuv3 *mhu, + unsigned int channel, + unsigned int doorbell) +{ + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + struct mbox_controller *mbox = &mhu->mbox; + struct mbox_chan *chans = mbox->chans; + + if (channel >= e->num_chans || doorbell >= MHUV3_FLAG_BITS) { + dev_err(mbox->dev, "Couldn't xlate to a valid channel (%d: %d)\n", + channel, doorbell); + return ERR_PTR(-ENODEV); + } + + return &chans[e->base_ch_idx + channel * MHUV3_FLAG_BITS + doorbell]; +} + +static void mhuv3_dbe_combined_irq_setup(struct mhuv3 *mhu) +{ + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + int i; + + if (mhu->frame == PBX_FRAME) { + struct pdbcw_page __iomem *dbcw = mhu->pbx->dbcw; + + for (i = 0; i < e->num_chans; i++) { + writel_relaxed_bitmask(0x1, &dbcw[i].int_clr, tfr_ack); + writel_relaxed_bitmask(0x0, &dbcw[i].int_en, tfr_ack); + writel_relaxed_bitmask(0x1, &dbcw[i].ctrl, comb_en); + } + } else { + struct mdbcw_page __iomem *dbcw = mhu->mbx->dbcw; + + for (i = 0; i < e->num_chans; i++) { + writel_relaxed(0xFFFFFFFF, &dbcw[i].clr); + writel_relaxed(0xFFFFFFFF, &dbcw[i].msk_set); + writel_relaxed_bitmask(0x1, &dbcw[i].ctrl, comb_en); + } + } +} + +static int mhuv3_dbe_channels_init(struct mhuv3 *mhu) +{ + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + struct mbox_controller *mbox = &mhu->mbox; + struct mbox_chan *chans; + int i; + + chans = mbox->chans + mbox->num_chans; + e->base_ch_idx = mbox->num_chans; + for (i = 0; i < e->num_chans; i++) { + struct mhuv3_mbox_chan_priv *priv; + int k; + + for (k = 0; k < MHUV3_FLAG_BITS; k++) { + priv = devm_kmalloc(mbox->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ch_idx = i; + priv->ops = &mhuv3_doorbell_ops; + priv->doorbell = k; + chans++->con_priv = priv; + mbox->num_chans++; + } + } + + spin_lock_init(&e->pending_lock); + + return 0; +} + +static bool mhuv3_dbe_doorbell_lookup(struct mhuv3 *mhu, unsigned int channel, + unsigned int *db) +{ + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + struct device *dev = mhu->mbox.dev; + u32 st; + + if (mhu->frame == PBX_FRAME) { + u32 active_dbs, fired_dbs; + + st = readl_relaxed_bitmask(&mhu->pbx->dbcw[channel].int_st, + tfr_ack); + if (!st) + goto err_spurious; + + active_dbs = readl_relaxed(&mhu->pbx->dbcw[channel].st); + scoped_guard(spinlock_irqsave, &e->pending_lock) { + fired_dbs = e->pending_db[channel] & ~active_dbs; + if (!fired_dbs) + goto err_spurious; + + *db = __ffs(fired_dbs); + e->pending_db[channel] &= ~BIT(*db); + } + fired_dbs &= ~BIT(*db); + /* Clear TFR Ack if no more doorbells pending */ + if (!fired_dbs) + writel_relaxed_bitmask(0x1, + &mhu->pbx->dbcw[channel].int_clr, + tfr_ack); + } else { + st = readl_relaxed(&mhu->mbx->dbcw[channel].st_msk); + if (!st) + goto err_spurious; + + *db = __ffs(st); + } + + return true; + +err_spurious: + dev_warn(dev, "Spurious IRQ on %s channel:%d\n", + mhuv3_str[mhu->frame], channel); + + return false; +} + +static struct mbox_chan *mhuv3_dbe_chan_from_comb_irq_get(struct mhuv3 *mhu) +{ + struct mhuv3_extension *e = mhu->ext[DBE_EXT]; + struct device *dev = mhu->mbox.dev; + int i; + + for (i = 0; i < MHUV3_DBCH_CMB_INT_ST_REG_CNT; i++) { + unsigned int channel, db; + u32 cmb_st; + + cmb_st = readl_relaxed(&mhu->ctrl->dbch_int_st[i]); + if (!cmb_st) + continue; + + channel = i * MHUV3_FLAG_BITS + __ffs(cmb_st); + if (channel >= e->num_chans) { + dev_err(dev, "Invalid %s channel:%d\n", + mhuv3_str[mhu->frame], channel); + return ERR_PTR(-EIO); + } + + if (!mhuv3_dbe_doorbell_lookup(mhu, channel, &db)) + continue; + + dev_dbg(dev, "Found %s ch[%d]/db[%d]\n", + mhuv3_str[mhu->frame], channel, db); + + return &mhu->mbox.chans[channel * MHUV3_FLAG_BITS + db]; + } + + return ERR_PTR(-EIO); +} + +static int mhuv3_dbe_init(struct mhuv3 *mhu) +{ + struct device *dev = mhu->mbox.dev; + struct mhuv3_extension *e; + + if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, dbe_spt)) + return 0; + + dev_dbg(dev, "%s: Initializing DBE Extension.\n", mhuv3_str[mhu->frame]); + + e = devm_kzalloc(dev, sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + + e->type = DBE_EXT; + /* Note that, by the spec, the number of channels is (num_dbch + 1) */ + e->num_chans = + readl_relaxed_bitmask(&mhu->ctrl->dbch_cfg0, num_dbch) + 1; + e->mbox_of_xlate = mhuv3_dbe_mbox_of_xlate; + e->combined_irq_setup = mhuv3_dbe_combined_irq_setup; + e->channels_init = mhuv3_dbe_channels_init; + e->chan_from_comb_irq_get = mhuv3_dbe_chan_from_comb_irq_get; + + mhu->num_chans += e->num_chans * MHUV3_FLAG_BITS; + mhu->ext[DBE_EXT] = e; + + dev_dbg(dev, "%s: found %d DBE channels.\n", + mhuv3_str[mhu->frame], e->num_chans); + + return 0; +} + +static int mhuv3_fce_init(struct mhuv3 *mhu) +{ + struct device *dev = mhu->mbox.dev; + + if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, fce_spt)) + return 0; + + dev_dbg(dev, "%s: FCE Extension not supported by driver.\n", + mhuv3_str[mhu->frame]); + + return 0; +} + +static int mhuv3_fe_init(struct mhuv3 *mhu) +{ + struct device *dev = mhu->mbox.dev; + + if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, fe_spt)) + return 0; + + dev_dbg(dev, "%s: FE Extension not supported by driver.\n", + mhuv3_str[mhu->frame]); + + return 0; +} + +static mhuv3_extension_initializer mhuv3_extension_init[NUM_EXT] = { + mhuv3_dbe_init, + mhuv3_fce_init, + mhuv3_fe_init, +}; + +static int mhuv3_initialize_channels(struct device *dev, struct mhuv3 *mhu) +{ + struct mbox_controller *mbox = &mhu->mbox; + int i, ret = 0; + + mbox->chans = devm_kcalloc(dev, mhu->num_chans, + sizeof(*mbox->chans), GFP_KERNEL); + if (!mbox->chans) + return dev_err_probe(dev, -ENOMEM, + "Failed to initialize channels\n"); + + for (i = 0; i < NUM_EXT && !ret; i++) + if (mhu->ext[i]) + ret = mhu->ext[i]->channels_init(mhu); + + return ret; +} + +static struct mbox_chan *mhuv3_mbox_of_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *pa) +{ + struct mhuv3 *mhu = mhu_from_mbox(mbox); + unsigned int type, channel, param; + + if (pa->args_count != MHUV3_MBOX_CELLS) + return ERR_PTR(-EINVAL); + + type = pa->args[MHUV3_MBOX_CELL_TYPE]; + if (type >= NUM_EXT) + return ERR_PTR(-EINVAL); + + channel = pa->args[MHUV3_MBOX_CELL_CHWN]; + param = pa->args[MHUV3_MBOX_CELL_PARAM]; + + return mhu->ext[type]->mbox_of_xlate(mhu, channel, param); +} + +static void mhu_frame_cleanup_actions(void *data) +{ + struct mhuv3 *mhu = data; + + writel_relaxed_bitmask(0x0, &mhu->ctrl->x_ctrl, op_req); +} + +static int mhuv3_frame_init(struct mhuv3 *mhu, void __iomem *regs) +{ + struct device *dev = mhu->mbox.dev; + int i; + + mhu->ctrl = regs; + mhu->frame = readl_relaxed_bitmask(&mhu->ctrl->blk_id, id); + if (mhu->frame > MBX_FRAME) + return dev_err_probe(dev, -EINVAL, + "Invalid Frame type- %d\n", mhu->frame); + + mhu->major = readl_relaxed_bitmask(&mhu->ctrl->aidr, arch_major_rev); + mhu->minor = readl_relaxed_bitmask(&mhu->ctrl->aidr, arch_minor_rev); + mhu->implem = readl_relaxed_bitmask(&mhu->ctrl->iidr, implementer); + mhu->rev = readl_relaxed_bitmask(&mhu->ctrl->iidr, revision); + mhu->var = readl_relaxed_bitmask(&mhu->ctrl->iidr, variant); + mhu->prod_id = readl_relaxed_bitmask(&mhu->ctrl->iidr, product_id); + if (mhu->major != MHUV3_MAJOR_VERSION) + return dev_err_probe(dev, -EINVAL, + "Unsupported MHU %s block - major:%d minor:%d\n", + mhuv3_str[mhu->frame], mhu->major, + mhu->minor); + + mhu->auto_op_full = + !!readl_relaxed_bitmask(&mhu->ctrl->feat_spt1, auto_op_spt); + /* Request the PBX/MBX to remain operational */ + if (mhu->auto_op_full) { + writel_relaxed_bitmask(0x1, &mhu->ctrl->x_ctrl, op_req); + devm_add_action_or_reset(dev, mhu_frame_cleanup_actions, mhu); + } + + dev_dbg(dev, + "Found MHU %s block - major:%d minor:%d\n implem:0x%X rev:0x%X var:0x%X prod_id:0x%X", + mhuv3_str[mhu->frame], mhu->major, mhu->minor, + mhu->implem, mhu->rev, mhu->var, mhu->prod_id); + + if (mhu->frame == PBX_FRAME) + mhu->pbx = regs; + else + mhu->mbx = regs; + + for (i = 0; i < NUM_EXT; i++) { + int ret; + + /* + * Note that extensions initialization fails only when such + * extension initialization routine fails and the extensions + * was found to be supported in hardware and in software. + */ + ret = mhuv3_extension_init[i](mhu); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize %s %s\n", + mhuv3_str[mhu->frame], + mhuv3_ext_str[i]); + } + + return 0; +} + +static irqreturn_t mhuv3_pbx_comb_interrupt(int irq, void *arg) +{ + unsigned int i, found = 0; + struct mhuv3 *mhu = arg; + struct mbox_chan *chan; + struct device *dev; + int ret = IRQ_NONE; + + dev = mhu->mbox.dev; + for (i = 0; i < NUM_EXT; i++) { + struct mhuv3_mbox_chan_priv *priv; + + /* FCE does not participate to the PBX combined */ + if (i == FCE_EXT || !mhu->ext[i]) + continue; + + chan = mhu->ext[i]->chan_from_comb_irq_get(mhu); + if (IS_ERR(chan)) + continue; + + found++; + priv = chan->con_priv; + if (!chan->cl) { + dev_warn(dev, "TX Ack on UNBOUND channel (%u)\n", + priv->ch_idx); + continue; + } + + mbox_chan_txdone(chan, 0); + ret = IRQ_HANDLED; + } + + if (found == 0) + dev_warn_once(dev, "Failed to find channel for the TX interrupt\n"); + + return ret; +} + +static irqreturn_t mhuv3_mbx_comb_interrupt(int irq, void *arg) +{ + unsigned int i, found = 0; + struct mhuv3 *mhu = arg; + struct mbox_chan *chan; + struct device *dev; + int ret = IRQ_NONE; + + dev = mhu->mbox.dev; + for (i = 0; i < NUM_EXT; i++) { + struct mhuv3_mbox_chan_priv *priv; + void *data __free(kfree) = NULL; + + if (!mhu->ext[i]) + continue; + + /* Process any extension which could be source of the IRQ */ + chan = mhu->ext[i]->chan_from_comb_irq_get(mhu); + if (IS_ERR(chan)) + continue; + + found++; + /* From here on we need to call rx_complete even on error */ + priv = chan->con_priv; + if (!chan->cl) { + dev_warn(dev, "RX Data on UNBOUND channel (%u)\n", + priv->ch_idx); + goto rx_ack; + } + + /* Read optional in-band LE data first. */ + if (priv->ops->read_data) { + data = priv->ops->read_data(mhu, chan); + if (IS_ERR(data)) { + dev_err(dev, + "Failed to read in-band data. err:%ld\n", + PTR_ERR(data)); + goto rx_ack; + } + } + + mbox_chan_received_data(chan, data); + ret = IRQ_HANDLED; + + /* + * Acknowledge transfer after any possible optional + * out-of-band data has also been retrieved via + * mbox_chan_received_data(). + */ +rx_ack: + if (priv->ops->rx_complete) + priv->ops->rx_complete(mhu, chan); + } + + if (found == 0) + dev_warn_once(dev, "Failed to find channel for the RX interrupt\n"); + + return ret; +} + +static int mhuv3_setup_pbx(struct mhuv3 *mhu) +{ + struct device *dev = mhu->mbox.dev; + + mhu->mbox.ops = &mhuv3_sender_ops; + + if (mhu->cmb_irq > 0) { + int ret, i; + + ret = devm_request_threaded_irq(dev, mhu->cmb_irq, NULL, + mhuv3_pbx_comb_interrupt, + IRQF_ONESHOT, "mhuv3-pbx", mhu); + if (ret) + return ret; + + mhu->mbox.txdone_irq = true; + mhu->mbox.txdone_poll = false; + + for (i = 0; i < NUM_EXT; i++) + if (mhu->ext[i]) + mhu->ext[i]->combined_irq_setup(mhu); + + dev_dbg(dev, "MHUv3 PBX IRQs initialized.\n"); + + return 0; + } + + dev_info(dev, "Using PBX in Tx polling mode.\n"); + mhu->mbox.txdone_irq = false; + mhu->mbox.txdone_poll = true; + mhu->mbox.txpoll_period = 1; + + return 0; +} + +static int mhuv3_setup_mbx(struct mhuv3 *mhu) +{ + struct device *dev = mhu->mbox.dev; + int ret, i; + + mhu->mbox.ops = &mhuv3_receiver_ops; + + if (mhu->cmb_irq <= 0) + return dev_err_probe(dev, -EINVAL, + "MBX combined IRQ is missing !\n"); + + ret = devm_request_threaded_irq(dev, mhu->cmb_irq, NULL, + mhuv3_mbx_comb_interrupt, IRQF_ONESHOT, + "mhuv3-mbx", mhu); + if (ret) + return ret; + + for (i = 0; i < NUM_EXT; i++) + if (mhu->ext[i]) + mhu->ext[i]->combined_irq_setup(mhu); + + dev_dbg(dev, "MHUv3 MBX IRQs initialized.\n"); + + return ret; +} + +static int mhuv3_irqs_init(struct mhuv3 *mhu, struct platform_device *pdev) +{ + dev_dbg(mhu->mbox.dev, "Initializing %s block.\n", + mhuv3_str[mhu->frame]); + + if (mhu->frame == PBX_FRAME) { + mhu->cmb_irq = + platform_get_irq_byname_optional(pdev, "combined"); + return mhuv3_setup_pbx(mhu); + } + + mhu->cmb_irq = platform_get_irq_byname(pdev, "combined"); + return mhuv3_setup_mbx(mhu); +} + +static int mhuv3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + void __iomem *regs; + struct mhuv3 *mhu; + int ret; + + mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL); + if (!mhu) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mhu->mbox.dev = dev; + ret = mhuv3_frame_init(mhu, regs); + if (ret) + return ret; + + ret = mhuv3_irqs_init(mhu, pdev); + if (ret) + return ret; + + mhu->mbox.of_xlate = mhuv3_mbox_of_xlate; + ret = mhuv3_initialize_channels(dev, mhu); + if (ret) + return ret; + + ret = devm_mbox_controller_register(dev, &mhu->mbox); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register ARM MHUv3 driver\n"); + + return ret; +} + +static const struct of_device_id mhuv3_of_match[] = { + { .compatible = "arm,mhuv3", .data = NULL }, + {} +}; +MODULE_DEVICE_TABLE(of, mhuv3_of_match); + +static struct platform_driver mhuv3_driver = { + .driver = { + .name = "arm-mhuv3-mailbox", + .of_match_table = mhuv3_of_match, + }, + .probe = mhuv3_probe, +}; +module_platform_driver(mhuv3_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ARM MHUv3 Driver"); +MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); diff --git a/drivers/mailbox/armada-37xx-rwtm-mailbox.c b/drivers/mailbox/armada-37xx-rwtm-mailbox.c new file mode 100644 index 000000000..047926711 --- /dev/null +++ b/drivers/mailbox/armada-37xx-rwtm-mailbox.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * rWTM BIU Mailbox driver for Armada 37xx + * + * Author: Marek Behún <kabel@kernel.org> + */ + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.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/armada-37xx-rwtm-mailbox.h> + +#define DRIVER_NAME "armada-37xx-rwtm-mailbox" + +/* relative to rWTM BIU Mailbox Registers */ +#define RWTM_MBOX_PARAM(i) (0x0 + ((i) << 2)) +#define RWTM_MBOX_COMMAND 0x40 +#define RWTM_MBOX_RETURN_STATUS 0x80 +#define RWTM_MBOX_STATUS(i) (0x84 + ((i) << 2)) +#define RWTM_MBOX_FIFO_STATUS 0xc4 +#define FIFO_STS_RDY 0x100 +#define FIFO_STS_CNTR_MASK 0x7 +#define FIFO_STS_CNTR_MAX 4 + +#define RWTM_HOST_INT_RESET 0xc8 +#define RWTM_HOST_INT_MASK 0xcc +#define SP_CMD_COMPLETE BIT(0) +#define SP_CMD_QUEUE_FULL_ACCESS BIT(17) +#define SP_CMD_QUEUE_FULL BIT(18) + +struct a37xx_mbox { + struct device *dev; + struct mbox_controller controller; + void __iomem *base; + int irq; +}; + +static void a37xx_mbox_receive(struct mbox_chan *chan) +{ + struct a37xx_mbox *mbox = chan->con_priv; + struct armada_37xx_rwtm_rx_msg rx_msg; + int i; + + rx_msg.retval = readl(mbox->base + RWTM_MBOX_RETURN_STATUS); + for (i = 0; i < 16; ++i) + rx_msg.status[i] = readl(mbox->base + RWTM_MBOX_STATUS(i)); + + mbox_chan_received_data(chan, &rx_msg); +} + +static irqreturn_t a37xx_mbox_irq_handler(int irq, void *data) +{ + struct mbox_chan *chan = data; + struct a37xx_mbox *mbox = chan->con_priv; + u32 reg; + + reg = readl(mbox->base + RWTM_HOST_INT_RESET); + + if (reg & SP_CMD_COMPLETE) + a37xx_mbox_receive(chan); + + if (reg & (SP_CMD_QUEUE_FULL_ACCESS | SP_CMD_QUEUE_FULL)) + dev_err(mbox->dev, "Secure processor command queue full\n"); + + writel(reg, mbox->base + RWTM_HOST_INT_RESET); + if (reg) + mbox_chan_txdone(chan, 0); + + return reg ? IRQ_HANDLED : IRQ_NONE; +} + +static int a37xx_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct a37xx_mbox *mbox = chan->con_priv; + struct armada_37xx_rwtm_tx_msg *msg = data; + int i; + u32 reg; + + if (!data) + return -EINVAL; + + reg = readl(mbox->base + RWTM_MBOX_FIFO_STATUS); + if (!(reg & FIFO_STS_RDY)) + dev_warn(mbox->dev, "Secure processor not ready\n"); + + if ((reg & FIFO_STS_CNTR_MASK) >= FIFO_STS_CNTR_MAX) { + dev_err(mbox->dev, "Secure processor command queue full\n"); + return -EBUSY; + } + + for (i = 0; i < 16; ++i) + writel(msg->args[i], mbox->base + RWTM_MBOX_PARAM(i)); + writel(msg->command, mbox->base + RWTM_MBOX_COMMAND); + + return 0; +} + +static int a37xx_mbox_startup(struct mbox_chan *chan) +{ + struct a37xx_mbox *mbox = chan->con_priv; + u32 reg; + int ret; + + ret = devm_request_irq(mbox->dev, mbox->irq, a37xx_mbox_irq_handler, 0, + DRIVER_NAME, chan); + if (ret < 0) + return ret; + + /* enable IRQ generation */ + reg = readl(mbox->base + RWTM_HOST_INT_MASK); + reg &= ~(SP_CMD_COMPLETE | SP_CMD_QUEUE_FULL_ACCESS | SP_CMD_QUEUE_FULL); + writel(reg, mbox->base + RWTM_HOST_INT_MASK); + + return 0; +} + +static void a37xx_mbox_shutdown(struct mbox_chan *chan) +{ + u32 reg; + struct a37xx_mbox *mbox = chan->con_priv; + + /* disable interrupt generation */ + reg = readl(mbox->base + RWTM_HOST_INT_MASK); + reg |= SP_CMD_COMPLETE | SP_CMD_QUEUE_FULL_ACCESS | SP_CMD_QUEUE_FULL; + writel(reg, mbox->base + RWTM_HOST_INT_MASK); + + devm_free_irq(mbox->dev, mbox->irq, chan); +} + +static const struct mbox_chan_ops a37xx_mbox_ops = { + .send_data = a37xx_mbox_send_data, + .startup = a37xx_mbox_startup, + .shutdown = a37xx_mbox_shutdown, +}; + +static int armada_37xx_mbox_probe(struct platform_device *pdev) +{ + struct a37xx_mbox *mbox; + struct mbox_chan *chans; + int ret; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + /* Allocated one channel */ + chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mbox->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->base)) + return PTR_ERR(mbox->base); + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq < 0) + return mbox->irq; + + mbox->dev = &pdev->dev; + + /* Hardware supports only one channel. */ + chans[0].con_priv = mbox; + mbox->controller.dev = mbox->dev; + mbox->controller.num_chans = 1; + mbox->controller.chans = chans; + mbox->controller.ops = &a37xx_mbox_ops; + mbox->controller.txdone_irq = true; + + ret = devm_mbox_controller_register(mbox->dev, &mbox->controller); + if (ret) { + dev_err(&pdev->dev, "Could not register mailbox controller\n"); + return ret; + } + + platform_set_drvdata(pdev, mbox); + return ret; +} + + +static const struct of_device_id armada_37xx_mbox_match[] = { + { .compatible = "marvell,armada-3700-rwtm-mailbox" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, armada_37xx_mbox_match); + +static struct platform_driver armada_37xx_mbox_driver = { + .probe = armada_37xx_mbox_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = armada_37xx_mbox_match, + }, +}; + +module_platform_driver(armada_37xx_mbox_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("rWTM BIU Mailbox driver for Armada 37xx"); +MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); diff --git a/drivers/mailbox/ast2700-mailbox.c b/drivers/mailbox/ast2700-mailbox.c new file mode 100644 index 000000000..83c6afe54 --- /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/axiado-mailbox.c b/drivers/mailbox/axiado-mailbox.c new file mode 100644 index 000000000..3a3cf7421 --- /dev/null +++ b/drivers/mailbox/axiado-mailbox.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation (or its affiliates). + */ + +#include <linux/atomic.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/bitfield.h> +#include <linux/debugfs.h> +#include <linux/iopoll.h> +#include <linux/mailbox_controller.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/unaligned.h> + +#define AXIADO_MBOX_TX_CHANS 8 /* 0-7 */ +#define AXIADO_MBOX_RX_CHANS 8 /* 8-15 */ +#define AXIADO_MBOX_CHAN_STRIDE 0x4 +#define AXIADO_MBOX_TX_REG_STRIDE 0x40 +#define AXIADO_MBOX_RX_REG_STRIDE 0x30 +#define AXIADO_MBOX_RX_POLL_US 10 +#define AXIADO_MBOX_RX_TIMEOUT_US 1000 + +/* Mailbox CSR bit definitions */ +#define AXIADO_MBOX_CSR_EMPTY BIT(0) /* 1 = FIFO empty; 0 = data available */ +#define AXIADO_MBOX_CSR_OVERFLOW BIT(2) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_UNDERFLOW BIT(3) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_FLUSH BIT(4) /* W1TRG flush; startup/shutdown */ +#define AXIADO_MBOX_CSR_LEVEL GENMASK(11, 5) + +#define AXIADO_MBOX_CSR_ERRORS (AXIADO_MBOX_CSR_OVERFLOW | \ + AXIADO_MBOX_CSR_UNDERFLOW) + +struct axiado_mbox_data { + u8 num_chans; + u16 msg_size; +}; + +struct axiado_channel_data { + void __iomem *mbox_reg; + void __iomem *csr_reg; + void *rx_buffer; + u8 channel_num; + int irq; + struct mbox_chan *chan; + bool active; + atomic_t fifo_errors; +}; + +/* mailbox side */ +struct axiado_mbox { + struct mbox_controller mbox; + const struct axiado_mbox_data *drv_data; + void __iomem *tx_base; + void __iomem *rx_base; + struct dentry *debugfs_dir; +}; + +/* + * Checks OVERFLOW/UNDERFLOW, logs and W1C-clears them if set. Shared by the + * TX and RX paths; RX additionally flushes the FIFO afterwards to resync + * framing, which TX must not do since it could discard data the peer has + * not read yet. + */ +static bool axiado_mbox_clear_fifo_errors(struct axiado_channel_data *priv) +{ + struct device *dev = priv->chan->mbox->dev; + u32 errors; + + errors = readl(priv->csr_reg) & AXIADO_MBOX_CSR_ERRORS; + if (!errors) + return false; + + writel(errors, priv->csr_reg); + atomic_inc(&priv->fifo_errors); + + dev_warn_ratelimited(dev, "Channel %u FIFO error: %#x\n", + priv->channel_num, errors); + + return true; +} + +static int axiado_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + struct axiado_channel_data *priv = chan->con_priv; + unsigned int idx = priv->channel_num; + u8 tail[sizeof(u32)] = { 0 }; + const u8 *buf = data; + unsigned int tail_len; + unsigned int offset; + u32 msg_len; + + if (!data) + return -EINVAL; + + /* + * The Axiado mailbox message ABI stores the total message length, + * including this length word, as a little-endian byte count in the + * first word of every message. + */ + msg_len = get_unaligned_le32(data); + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + return -EINVAL; + + /* Only touch the hardware after validating the message. */ + axiado_mbox_clear_fifo_errors(priv); + + if (!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY)) { + dev_warn_ratelimited(mb->mbox.dev, "Channel %u is busy\n", idx); + return -EBUSY; + } + + for (offset = 0; offset + sizeof(u32) <= msg_len; + offset += sizeof(u32)) + writel(get_unaligned_le32(buf + offset), priv->mbox_reg); + + tail_len = msg_len - offset; + if (tail_len) { + memcpy(tail, buf + offset, tail_len); + writel(get_unaligned_le32(tail), priv->mbox_reg); + } + + dev_dbg(mb->mbox.dev, "%s: Ch-%u sent\n", __func__, idx); + + return 0; +} + +static irqreturn_t axiado_rx_thread(int irq, void *dev_id) +{ + struct axiado_channel_data *priv = dev_id; + struct mbox_chan *chan = priv->chan; + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + u8 *buf = priv->rx_buffer; + unsigned int num_words; + unsigned int remaining; + unsigned int i; + u32 msg_len; + u32 word; + u32 csr; + int ret; + + /* + * Clear and log/count any pending errors, but don't discard data + * on their account alone: a rejected overflow write doesn't + * corrupt what was already safely queued ahead of it, so let the + * length-based read below decide whether what's here is usable. + */ + axiado_mbox_clear_fifo_errors(priv); + + csr = readl(priv->csr_reg); + if (csr & AXIADO_MBOX_CSR_EMPTY) + return IRQ_NONE; + + /* + * The first word contains the total message length in bytes, + * including the length word itself. + */ + word = readl(priv->mbox_reg); + msg_len = word; + put_unaligned_le32(word, buf); + + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + goto invalid_message; + + num_words = DIV_ROUND_UP(msg_len, sizeof(u32)); + remaining = num_words - 1; + + /* + * The not-empty interrupt may occur as soon as the first DW enters + * the FIFO. Wait until all remaining DWs of this message arrive. + */ + if (remaining) { + ret = readl_poll_timeout(priv->csr_reg, csr, + (csr & AXIADO_MBOX_CSR_ERRORS) || + FIELD_GET(AXIADO_MBOX_CSR_LEVEL, csr) >= + remaining, + AXIADO_MBOX_RX_POLL_US, + AXIADO_MBOX_RX_TIMEOUT_US); + if (ret) + goto incomplete_message; + + axiado_mbox_clear_fifo_errors(priv); + } + + for (i = 1; i < num_words; i++) { + word = readl(priv->mbox_reg); + put_unaligned_le32(word, buf + i * sizeof(u32)); + } + + axiado_mbox_clear_fifo_errors(priv); + + if (READ_ONCE(priv->active)) + mbox_chan_received_data(chan, priv->rx_buffer); + + return IRQ_HANDLED; + +incomplete_message: + dev_warn_ratelimited(chan->mbox->dev, + "Channel %u received an incomplete message\n", + priv->channel_num); + +invalid_message: + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + return IRQ_HANDLED; +} + +static int axiado_mbox_startup(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + /* + * Only flush on a genuine error. A blind flush here would discard + * a message the peer legitimately sent before this side started + * up (e.g. during normal boot sequencing), which is not corrupt + * and does not need resyncing. + */ + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + WRITE_ONCE(priv->active, true); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + enable_irq(priv->irq); + + return 0; +} + +static void axiado_mbox_shutdown(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + WRITE_ONCE(priv->active, false); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + disable_irq(priv->irq); + + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); +} + +static bool axiado_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + return !!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY); +} + +static const struct mbox_chan_ops axiado_mbox_chan_ops = { + .send_data = axiado_mbox_send_data, + .startup = axiado_mbox_startup, + .shutdown = axiado_mbox_shutdown, + .last_tx_done = axiado_mbox_last_tx_done, +}; + +static void axiado_mbox_debugfs_remove(void *dentry) +{ + debugfs_remove_recursive(dentry); +} + +static int axiado_mbox_probe(struct platform_device *pdev) +{ + const struct axiado_mbox_data *drv_data; + struct axiado_channel_data *ch_data; + struct device *dev = &pdev->dev; + struct axiado_mbox *mb; + unsigned int irq_idx = 0; + unsigned int rx_chan; + unsigned int i; + int ret; + + drv_data = device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + + mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox.dev = dev; + mb->mbox.num_chans = drv_data->num_chans; + + mb->mbox.chans = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*mb->mbox.chans), + GFP_KERNEL); + if (!mb->mbox.chans) + return -ENOMEM; + + mb->tx_base = devm_platform_ioremap_resource_byname(pdev, "tx"); + if (IS_ERR(mb->tx_base)) + return PTR_ERR(mb->tx_base); + + mb->rx_base = devm_platform_ioremap_resource_byname(pdev, "rx"); + if (IS_ERR(mb->rx_base)) + return PTR_ERR(mb->rx_base); + + ch_data = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*ch_data), + GFP_KERNEL); + if (!ch_data) + return -ENOMEM; + + mb->debugfs_dir = debugfs_create_dir(dev_name(dev), NULL); + ret = devm_add_action_or_reset(dev, axiado_mbox_debugfs_remove, + mb->debugfs_dir); + if (ret) + return ret; + + for (i = 0; i < drv_data->num_chans; i++) { + char name[16]; + + ch_data[i].channel_num = i; + ch_data[i].chan = &mb->mbox.chans[i]; + + mb->mbox.chans[i].con_priv = &ch_data[i]; + + snprintf(name, sizeof(name), "chan%u-errors", i); + debugfs_create_atomic_t(name, 0444, mb->debugfs_dir, + &ch_data[i].fifo_errors); + + if (i < AXIADO_MBOX_TX_CHANS) { + ch_data[i].mbox_reg = mb->tx_base + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->tx_base + AXIADO_MBOX_TX_REG_STRIDE + + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = -1; + continue; + } + + ch_data[i].rx_buffer = devm_kzalloc(dev, drv_data->msg_size, + GFP_KERNEL); + if (!ch_data[i].rx_buffer) + return -ENOMEM; + + rx_chan = i - AXIADO_MBOX_TX_CHANS; + ch_data[i].mbox_reg = mb->rx_base + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->rx_base + AXIADO_MBOX_RX_REG_STRIDE + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = platform_get_irq(pdev, irq_idx++); + if (ch_data[i].irq < 0) + return dev_err_probe(dev, ch_data[i].irq, + "Failed to get IRQ for channel %u\n", i); + + ret = devm_request_threaded_irq(dev, ch_data[i].irq, NULL, + axiado_rx_thread, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(dev), &ch_data[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to request IRQ for channel %u\n", i); + + dev_dbg(dev, "RX chan %u -> irq %d\n", i, ch_data[i].irq); + } + + platform_set_drvdata(pdev, mb); + mb->drv_data = drv_data; + mb->mbox.ops = &axiado_mbox_chan_ops; + mb->mbox.txdone_irq = false; + mb->mbox.txdone_poll = true; + mb->mbox.txpoll_period = 5; + + return devm_mbox_controller_register(dev, &mb->mbox); +} + +static const struct axiado_mbox_data axiado_drv_data = { + .num_chans = AXIADO_MBOX_TX_CHANS + AXIADO_MBOX_RX_CHANS, + .msg_size = 256, +}; + +static const struct of_device_id axiado_mbox_of_match[] = { + { .compatible = "axiado,ax3005-mailbox", .data = &axiado_drv_data }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_mbox_of_match); + +static struct platform_driver axiado_mbox_driver = { + .driver = { + .name = "axiado-mailbox", + .of_match_table = axiado_mbox_of_match, + }, + .probe = axiado_mbox_probe, +}; +module_platform_driver(axiado_mbox_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado Mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/bcm-flexrm-mailbox.c b/drivers/mailbox/bcm-flexrm-mailbox.c new file mode 100644 index 000000000..04c47eadf --- /dev/null +++ b/drivers/mailbox/bcm-flexrm-mailbox.c @@ -0,0 +1,1673 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2017 Broadcom + +/* + * Broadcom FlexRM Mailbox Driver + * + * Each Broadcom FlexSparx4 offload engine is implemented as an + * extension to Broadcom FlexRM ring manager. The FlexRM ring + * manager provides a set of rings which can be used to submit + * work to a FlexSparx4 offload engine. + * + * This driver creates a mailbox controller using a set of FlexRM + * rings where each mailbox channel represents a separate FlexRM ring. + */ + +#include <asm/barrier.h> +#include <asm/byteorder.h> +#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/brcm-message.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +/* ====== FlexRM register defines ===== */ + +/* FlexRM configuration */ +#define RING_REGS_SIZE 0x10000 +#define RING_DESC_SIZE 8 +#define RING_DESC_INDEX(offset) \ + ((offset) / RING_DESC_SIZE) +#define RING_DESC_OFFSET(index) \ + ((index) * RING_DESC_SIZE) +#define RING_MAX_REQ_COUNT 1024 +#define RING_BD_ALIGN_ORDER 12 +#define RING_BD_ALIGN_CHECK(addr) \ + (!((addr) & ((0x1 << RING_BD_ALIGN_ORDER) - 1))) +#define RING_BD_TOGGLE_INVALID(offset) \ + (((offset) >> RING_BD_ALIGN_ORDER) & 0x1) +#define RING_BD_TOGGLE_VALID(offset) \ + (!RING_BD_TOGGLE_INVALID(offset)) +#define RING_BD_DESC_PER_REQ 32 +#define RING_BD_DESC_COUNT \ + (RING_MAX_REQ_COUNT * RING_BD_DESC_PER_REQ) +#define RING_BD_SIZE \ + (RING_BD_DESC_COUNT * RING_DESC_SIZE) +#define RING_CMPL_ALIGN_ORDER 13 +#define RING_CMPL_DESC_COUNT RING_MAX_REQ_COUNT +#define RING_CMPL_SIZE \ + (RING_CMPL_DESC_COUNT * RING_DESC_SIZE) +#define RING_VER_MAGIC 0x76303031 + +/* Per-Ring register offsets */ +#define RING_VER 0x000 +#define RING_BD_START_ADDR 0x004 +#define RING_BD_READ_PTR 0x008 +#define RING_BD_WRITE_PTR 0x00c +#define RING_BD_READ_PTR_DDR_LS 0x010 +#define RING_BD_READ_PTR_DDR_MS 0x014 +#define RING_CMPL_START_ADDR 0x018 +#define RING_CMPL_WRITE_PTR 0x01c +#define RING_NUM_REQ_RECV_LS 0x020 +#define RING_NUM_REQ_RECV_MS 0x024 +#define RING_NUM_REQ_TRANS_LS 0x028 +#define RING_NUM_REQ_TRANS_MS 0x02c +#define RING_NUM_REQ_OUTSTAND 0x030 +#define RING_CONTROL 0x034 +#define RING_FLUSH_DONE 0x038 +#define RING_MSI_ADDR_LS 0x03c +#define RING_MSI_ADDR_MS 0x040 +#define RING_MSI_CONTROL 0x048 +#define RING_BD_READ_PTR_DDR_CONTROL 0x04c +#define RING_MSI_DATA_VALUE 0x064 + +/* Register RING_BD_START_ADDR fields */ +#define BD_LAST_UPDATE_HW_SHIFT 28 +#define BD_LAST_UPDATE_HW_MASK 0x1 +#define BD_START_ADDR_VALUE(pa) \ + ((u32)((((dma_addr_t)(pa)) >> RING_BD_ALIGN_ORDER) & 0x0fffffff)) +#define BD_START_ADDR_DECODE(val) \ + ((dma_addr_t)((val) & 0x0fffffff) << RING_BD_ALIGN_ORDER) + +/* Register RING_CMPL_START_ADDR fields */ +#define CMPL_START_ADDR_VALUE(pa) \ + ((u32)((((u64)(pa)) >> RING_CMPL_ALIGN_ORDER) & 0x07ffffff)) + +/* Register RING_CONTROL fields */ +#define CONTROL_MASK_DISABLE_CONTROL 12 +#define CONTROL_FLUSH_SHIFT 5 +#define CONTROL_ACTIVE_SHIFT 4 +#define CONTROL_RATE_ADAPT_MASK 0xf +#define CONTROL_RATE_DYNAMIC 0x0 +#define CONTROL_RATE_FAST 0x8 +#define CONTROL_RATE_MEDIUM 0x9 +#define CONTROL_RATE_SLOW 0xa +#define CONTROL_RATE_IDLE 0xb + +/* Register RING_FLUSH_DONE fields */ +#define FLUSH_DONE_MASK 0x1 + +/* Register RING_MSI_CONTROL fields */ +#define MSI_TIMER_VAL_SHIFT 16 +#define MSI_TIMER_VAL_MASK 0xffff +#define MSI_ENABLE_SHIFT 15 +#define MSI_ENABLE_MASK 0x1 +#define MSI_COUNT_SHIFT 0 +#define MSI_COUNT_MASK 0x3ff + +/* Register RING_BD_READ_PTR_DDR_CONTROL fields */ +#define BD_READ_PTR_DDR_TIMER_VAL_SHIFT 16 +#define BD_READ_PTR_DDR_TIMER_VAL_MASK 0xffff +#define BD_READ_PTR_DDR_ENABLE_SHIFT 15 +#define BD_READ_PTR_DDR_ENABLE_MASK 0x1 + +/* ====== FlexRM ring descriptor defines ===== */ + +/* Completion descriptor format */ +#define CMPL_OPAQUE_SHIFT 0 +#define CMPL_OPAQUE_MASK 0xffff +#define CMPL_ENGINE_STATUS_SHIFT 16 +#define CMPL_ENGINE_STATUS_MASK 0xffff +#define CMPL_DME_STATUS_SHIFT 32 +#define CMPL_DME_STATUS_MASK 0xffff +#define CMPL_RM_STATUS_SHIFT 48 +#define CMPL_RM_STATUS_MASK 0xffff + +/* Completion DME status code */ +#define DME_STATUS_MEM_COR_ERR BIT(0) +#define DME_STATUS_MEM_UCOR_ERR BIT(1) +#define DME_STATUS_FIFO_UNDERFLOW BIT(2) +#define DME_STATUS_FIFO_OVERFLOW BIT(3) +#define DME_STATUS_RRESP_ERR BIT(4) +#define DME_STATUS_BRESP_ERR BIT(5) +#define DME_STATUS_ERROR_MASK (DME_STATUS_MEM_COR_ERR | \ + DME_STATUS_MEM_UCOR_ERR | \ + DME_STATUS_FIFO_UNDERFLOW | \ + DME_STATUS_FIFO_OVERFLOW | \ + DME_STATUS_RRESP_ERR | \ + DME_STATUS_BRESP_ERR) + +/* Completion RM status code */ +#define RM_STATUS_CODE_SHIFT 0 +#define RM_STATUS_CODE_MASK 0x3ff +#define RM_STATUS_CODE_GOOD 0x0 +#define RM_STATUS_CODE_AE_TIMEOUT 0x3ff + +/* General descriptor format */ +#define DESC_TYPE_SHIFT 60 +#define DESC_TYPE_MASK 0xf +#define DESC_PAYLOAD_SHIFT 0 +#define DESC_PAYLOAD_MASK 0x0fffffffffffffff + +/* Null descriptor format */ +#define NULL_TYPE 0 +#define NULL_TOGGLE_SHIFT 58 +#define NULL_TOGGLE_MASK 0x1 + +/* Header descriptor format */ +#define HEADER_TYPE 1 +#define HEADER_TOGGLE_SHIFT 58 +#define HEADER_TOGGLE_MASK 0x1 +#define HEADER_ENDPKT_SHIFT 57 +#define HEADER_ENDPKT_MASK 0x1 +#define HEADER_STARTPKT_SHIFT 56 +#define HEADER_STARTPKT_MASK 0x1 +#define HEADER_BDCOUNT_SHIFT 36 +#define HEADER_BDCOUNT_MASK 0x1f +#define HEADER_BDCOUNT_MAX HEADER_BDCOUNT_MASK +#define HEADER_FLAGS_SHIFT 16 +#define HEADER_FLAGS_MASK 0xffff +#define HEADER_OPAQUE_SHIFT 0 +#define HEADER_OPAQUE_MASK 0xffff + +/* Source (SRC) descriptor format */ +#define SRC_TYPE 2 +#define SRC_LENGTH_SHIFT 44 +#define SRC_LENGTH_MASK 0xffff +#define SRC_ADDR_SHIFT 0 +#define SRC_ADDR_MASK 0x00000fffffffffff + +/* Destination (DST) descriptor format */ +#define DST_TYPE 3 +#define DST_LENGTH_SHIFT 44 +#define DST_LENGTH_MASK 0xffff +#define DST_ADDR_SHIFT 0 +#define DST_ADDR_MASK 0x00000fffffffffff + +/* Immediate (IMM) descriptor format */ +#define IMM_TYPE 4 +#define IMM_DATA_SHIFT 0 +#define IMM_DATA_MASK 0x0fffffffffffffff + +/* Next pointer (NPTR) descriptor format */ +#define NPTR_TYPE 5 +#define NPTR_TOGGLE_SHIFT 58 +#define NPTR_TOGGLE_MASK 0x1 +#define NPTR_ADDR_SHIFT 0 +#define NPTR_ADDR_MASK 0x00000fffffffffff + +/* Mega source (MSRC) descriptor format */ +#define MSRC_TYPE 6 +#define MSRC_LENGTH_SHIFT 44 +#define MSRC_LENGTH_MASK 0xffff +#define MSRC_ADDR_SHIFT 0 +#define MSRC_ADDR_MASK 0x00000fffffffffff + +/* Mega destination (MDST) descriptor format */ +#define MDST_TYPE 7 +#define MDST_LENGTH_SHIFT 44 +#define MDST_LENGTH_MASK 0xffff +#define MDST_ADDR_SHIFT 0 +#define MDST_ADDR_MASK 0x00000fffffffffff + +/* Source with tlast (SRCT) descriptor format */ +#define SRCT_TYPE 8 +#define SRCT_LENGTH_SHIFT 44 +#define SRCT_LENGTH_MASK 0xffff +#define SRCT_ADDR_SHIFT 0 +#define SRCT_ADDR_MASK 0x00000fffffffffff + +/* Destination with tlast (DSTT) descriptor format */ +#define DSTT_TYPE 9 +#define DSTT_LENGTH_SHIFT 44 +#define DSTT_LENGTH_MASK 0xffff +#define DSTT_ADDR_SHIFT 0 +#define DSTT_ADDR_MASK 0x00000fffffffffff + +/* Immediate with tlast (IMMT) descriptor format */ +#define IMMT_TYPE 10 +#define IMMT_DATA_SHIFT 0 +#define IMMT_DATA_MASK 0x0fffffffffffffff + +/* Descriptor helper macros */ +#define DESC_DEC(_d, _s, _m) (((_d) >> (_s)) & (_m)) +#define DESC_ENC(_d, _v, _s, _m) \ + do { \ + (_d) &= ~((u64)(_m) << (_s)); \ + (_d) |= (((u64)(_v) & (_m)) << (_s)); \ + } while (0) + +/* ====== FlexRM data structures ===== */ + +struct flexrm_ring { + /* Unprotected members */ + int num; + struct flexrm_mbox *mbox; + void __iomem *regs; + bool irq_requested; + unsigned int irq; + cpumask_t irq_aff_hint; + unsigned int msi_timer_val; + unsigned int msi_count_threshold; + struct brcm_message *requests[RING_MAX_REQ_COUNT]; + void *bd_base; + dma_addr_t bd_dma_base; + u32 bd_write_offset; + void *cmpl_base; + dma_addr_t cmpl_dma_base; + /* Atomic stats */ + atomic_t msg_send_count; + atomic_t msg_cmpl_count; + /* Protected members */ + spinlock_t lock; + DECLARE_BITMAP(requests_bmap, RING_MAX_REQ_COUNT); + u32 cmpl_read_offset; +}; + +struct flexrm_mbox { + struct device *dev; + void __iomem *regs; + u32 num_rings; + struct flexrm_ring *rings; + struct dma_pool *bd_pool; + struct dma_pool *cmpl_pool; + struct dentry *root; + struct mbox_controller controller; +}; + +/* ====== FlexRM ring descriptor helper routines ===== */ + +static u64 flexrm_read_desc(void *desc_ptr) +{ + return le64_to_cpu(*((u64 *)desc_ptr)); +} + +static void flexrm_write_desc(void *desc_ptr, u64 desc) +{ + *((u64 *)desc_ptr) = cpu_to_le64(desc); +} + +static u32 flexrm_cmpl_desc_to_reqid(u64 cmpl_desc) +{ + return (u32)(cmpl_desc & CMPL_OPAQUE_MASK); +} + +static int flexrm_cmpl_desc_to_error(u64 cmpl_desc) +{ + u32 status; + + status = DESC_DEC(cmpl_desc, CMPL_DME_STATUS_SHIFT, + CMPL_DME_STATUS_MASK); + if (status & DME_STATUS_ERROR_MASK) + return -EIO; + + status = DESC_DEC(cmpl_desc, CMPL_RM_STATUS_SHIFT, + CMPL_RM_STATUS_MASK); + status &= RM_STATUS_CODE_MASK; + if (status == RM_STATUS_CODE_AE_TIMEOUT) + return -ETIMEDOUT; + + return 0; +} + +static bool flexrm_is_next_table_desc(void *desc_ptr) +{ + u64 desc = flexrm_read_desc(desc_ptr); + u32 type = DESC_DEC(desc, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + + return (type == NPTR_TYPE) ? true : false; +} + +static u64 flexrm_next_table_desc(u32 toggle, dma_addr_t next_addr) +{ + u64 desc = 0; + + DESC_ENC(desc, NPTR_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, toggle, NPTR_TOGGLE_SHIFT, NPTR_TOGGLE_MASK); + DESC_ENC(desc, next_addr, NPTR_ADDR_SHIFT, NPTR_ADDR_MASK); + + return desc; +} + +static u64 flexrm_null_desc(u32 toggle) +{ + u64 desc = 0; + + DESC_ENC(desc, NULL_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, toggle, NULL_TOGGLE_SHIFT, NULL_TOGGLE_MASK); + + return desc; +} + +static u32 flexrm_estimate_header_desc_count(u32 nhcnt) +{ + u32 hcnt = nhcnt / HEADER_BDCOUNT_MAX; + + if (!(nhcnt % HEADER_BDCOUNT_MAX)) + hcnt += 1; + + return hcnt; +} + +static void flexrm_flip_header_toggle(void *desc_ptr) +{ + u64 desc = flexrm_read_desc(desc_ptr); + + if (desc & ((u64)0x1 << HEADER_TOGGLE_SHIFT)) + desc &= ~((u64)0x1 << HEADER_TOGGLE_SHIFT); + else + desc |= ((u64)0x1 << HEADER_TOGGLE_SHIFT); + + flexrm_write_desc(desc_ptr, desc); +} + +static u64 flexrm_header_desc(u32 toggle, u32 startpkt, u32 endpkt, + u32 bdcount, u32 flags, u32 opaque) +{ + u64 desc = 0; + + DESC_ENC(desc, HEADER_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, toggle, HEADER_TOGGLE_SHIFT, HEADER_TOGGLE_MASK); + DESC_ENC(desc, startpkt, HEADER_STARTPKT_SHIFT, HEADER_STARTPKT_MASK); + DESC_ENC(desc, endpkt, HEADER_ENDPKT_SHIFT, HEADER_ENDPKT_MASK); + DESC_ENC(desc, bdcount, HEADER_BDCOUNT_SHIFT, HEADER_BDCOUNT_MASK); + DESC_ENC(desc, flags, HEADER_FLAGS_SHIFT, HEADER_FLAGS_MASK); + DESC_ENC(desc, opaque, HEADER_OPAQUE_SHIFT, HEADER_OPAQUE_MASK); + + return desc; +} + +static void flexrm_enqueue_desc(u32 nhpos, u32 nhcnt, u32 reqid, + u64 desc, void **desc_ptr, u32 *toggle, + void *start_desc, void *end_desc) +{ + u64 d; + u32 nhavail, _toggle, _startpkt, _endpkt, _bdcount; + + /* Sanity check */ + if (nhcnt <= nhpos) + return; + + /* + * Each request or packet start with a HEADER descriptor followed + * by one or more non-HEADER descriptors (SRC, SRCT, MSRC, DST, + * DSTT, MDST, IMM, and IMMT). The number of non-HEADER descriptors + * following a HEADER descriptor is represented by BDCOUNT field + * of HEADER descriptor. The max value of BDCOUNT field is 31 which + * means we can only have 31 non-HEADER descriptors following one + * HEADER descriptor. + * + * In general use, number of non-HEADER descriptors can easily go + * beyond 31. To tackle this situation, we have packet (or request) + * extension bits (STARTPKT and ENDPKT) in the HEADER descriptor. + * + * To use packet extension, the first HEADER descriptor of request + * (or packet) will have STARTPKT=1 and ENDPKT=0. The intermediate + * HEADER descriptors will have STARTPKT=0 and ENDPKT=0. The last + * HEADER descriptor will have STARTPKT=0 and ENDPKT=1. Also, the + * TOGGLE bit of the first HEADER will be set to invalid state to + * ensure that FlexRM does not start fetching descriptors till all + * descriptors are enqueued. The user of this function will flip + * the TOGGLE bit of first HEADER after all descriptors are + * enqueued. + */ + + if ((nhpos % HEADER_BDCOUNT_MAX == 0) && (nhcnt - nhpos)) { + /* Prepare the header descriptor */ + nhavail = (nhcnt - nhpos); + _toggle = (nhpos == 0) ? !(*toggle) : (*toggle); + _startpkt = (nhpos == 0) ? 0x1 : 0x0; + _endpkt = (nhavail <= HEADER_BDCOUNT_MAX) ? 0x1 : 0x0; + _bdcount = (nhavail <= HEADER_BDCOUNT_MAX) ? + nhavail : HEADER_BDCOUNT_MAX; + if (nhavail <= HEADER_BDCOUNT_MAX) + _bdcount = nhavail; + else + _bdcount = HEADER_BDCOUNT_MAX; + d = flexrm_header_desc(_toggle, _startpkt, _endpkt, + _bdcount, 0x0, reqid); + + /* Write header descriptor */ + flexrm_write_desc(*desc_ptr, d); + + /* Point to next descriptor */ + *desc_ptr += sizeof(desc); + if (*desc_ptr == end_desc) + *desc_ptr = start_desc; + + /* Skip next pointer descriptors */ + while (flexrm_is_next_table_desc(*desc_ptr)) { + *toggle = (*toggle) ? 0 : 1; + *desc_ptr += sizeof(desc); + if (*desc_ptr == end_desc) + *desc_ptr = start_desc; + } + } + + /* Write desired descriptor */ + flexrm_write_desc(*desc_ptr, desc); + + /* Point to next descriptor */ + *desc_ptr += sizeof(desc); + if (*desc_ptr == end_desc) + *desc_ptr = start_desc; + + /* Skip next pointer descriptors */ + while (flexrm_is_next_table_desc(*desc_ptr)) { + *toggle = (*toggle) ? 0 : 1; + *desc_ptr += sizeof(desc); + if (*desc_ptr == end_desc) + *desc_ptr = start_desc; + } +} + +static u64 flexrm_src_desc(dma_addr_t addr, unsigned int length) +{ + u64 desc = 0; + + DESC_ENC(desc, SRC_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length, SRC_LENGTH_SHIFT, SRC_LENGTH_MASK); + DESC_ENC(desc, addr, SRC_ADDR_SHIFT, SRC_ADDR_MASK); + + return desc; +} + +static u64 flexrm_msrc_desc(dma_addr_t addr, unsigned int length_div_16) +{ + u64 desc = 0; + + DESC_ENC(desc, MSRC_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length_div_16, MSRC_LENGTH_SHIFT, MSRC_LENGTH_MASK); + DESC_ENC(desc, addr, MSRC_ADDR_SHIFT, MSRC_ADDR_MASK); + + return desc; +} + +static u64 flexrm_dst_desc(dma_addr_t addr, unsigned int length) +{ + u64 desc = 0; + + DESC_ENC(desc, DST_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length, DST_LENGTH_SHIFT, DST_LENGTH_MASK); + DESC_ENC(desc, addr, DST_ADDR_SHIFT, DST_ADDR_MASK); + + return desc; +} + +static u64 flexrm_mdst_desc(dma_addr_t addr, unsigned int length_div_16) +{ + u64 desc = 0; + + DESC_ENC(desc, MDST_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length_div_16, MDST_LENGTH_SHIFT, MDST_LENGTH_MASK); + DESC_ENC(desc, addr, MDST_ADDR_SHIFT, MDST_ADDR_MASK); + + return desc; +} + +static u64 flexrm_imm_desc(u64 data) +{ + u64 desc = 0; + + DESC_ENC(desc, IMM_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, data, IMM_DATA_SHIFT, IMM_DATA_MASK); + + return desc; +} + +static u64 flexrm_srct_desc(dma_addr_t addr, unsigned int length) +{ + u64 desc = 0; + + DESC_ENC(desc, SRCT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length, SRCT_LENGTH_SHIFT, SRCT_LENGTH_MASK); + DESC_ENC(desc, addr, SRCT_ADDR_SHIFT, SRCT_ADDR_MASK); + + return desc; +} + +static u64 flexrm_dstt_desc(dma_addr_t addr, unsigned int length) +{ + u64 desc = 0; + + DESC_ENC(desc, DSTT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, length, DSTT_LENGTH_SHIFT, DSTT_LENGTH_MASK); + DESC_ENC(desc, addr, DSTT_ADDR_SHIFT, DSTT_ADDR_MASK); + + return desc; +} + +static u64 flexrm_immt_desc(u64 data) +{ + u64 desc = 0; + + DESC_ENC(desc, IMMT_TYPE, DESC_TYPE_SHIFT, DESC_TYPE_MASK); + DESC_ENC(desc, data, IMMT_DATA_SHIFT, IMMT_DATA_MASK); + + return desc; +} + +static bool flexrm_spu_sanity_check(struct brcm_message *msg) +{ + struct scatterlist *sg; + + if (!msg->spu.src || !msg->spu.dst) + return false; + for (sg = msg->spu.src; sg; sg = sg_next(sg)) { + if (sg->length & 0xf) { + if (sg->length > SRC_LENGTH_MASK) + return false; + } else { + if (sg->length > (MSRC_LENGTH_MASK * 16)) + return false; + } + } + for (sg = msg->spu.dst; sg; sg = sg_next(sg)) { + if (sg->length & 0xf) { + if (sg->length > DST_LENGTH_MASK) + return false; + } else { + if (sg->length > (MDST_LENGTH_MASK * 16)) + return false; + } + } + + return true; +} + +static u32 flexrm_spu_estimate_nonheader_desc_count(struct brcm_message *msg) +{ + u32 cnt = 0; + unsigned int dst_target = 0; + struct scatterlist *src_sg = msg->spu.src, *dst_sg = msg->spu.dst; + + while (src_sg || dst_sg) { + if (src_sg) { + cnt++; + dst_target = src_sg->length; + src_sg = sg_next(src_sg); + } else + dst_target = UINT_MAX; + + while (dst_target && dst_sg) { + cnt++; + if (dst_sg->length < dst_target) + dst_target -= dst_sg->length; + else + dst_target = 0; + dst_sg = sg_next(dst_sg); + } + } + + return cnt; +} + +static int flexrm_spu_dma_map(struct device *dev, struct brcm_message *msg) +{ + int rc; + + rc = dma_map_sg(dev, msg->spu.src, sg_nents(msg->spu.src), + DMA_TO_DEVICE); + if (!rc) + return -EIO; + + rc = dma_map_sg(dev, msg->spu.dst, sg_nents(msg->spu.dst), + DMA_FROM_DEVICE); + if (!rc) { + dma_unmap_sg(dev, msg->spu.src, sg_nents(msg->spu.src), + DMA_TO_DEVICE); + return -EIO; + } + + return 0; +} + +static void flexrm_spu_dma_unmap(struct device *dev, struct brcm_message *msg) +{ + dma_unmap_sg(dev, msg->spu.dst, sg_nents(msg->spu.dst), + DMA_FROM_DEVICE); + dma_unmap_sg(dev, msg->spu.src, sg_nents(msg->spu.src), + DMA_TO_DEVICE); +} + +static void *flexrm_spu_write_descs(struct brcm_message *msg, u32 nhcnt, + u32 reqid, void *desc_ptr, u32 toggle, + void *start_desc, void *end_desc) +{ + u64 d; + u32 nhpos = 0; + void *orig_desc_ptr = desc_ptr; + unsigned int dst_target = 0; + struct scatterlist *src_sg = msg->spu.src, *dst_sg = msg->spu.dst; + + while (src_sg || dst_sg) { + if (src_sg) { + if (sg_dma_len(src_sg) & 0xf) + d = flexrm_src_desc(sg_dma_address(src_sg), + sg_dma_len(src_sg)); + else + d = flexrm_msrc_desc(sg_dma_address(src_sg), + sg_dma_len(src_sg)/16); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + dst_target = sg_dma_len(src_sg); + src_sg = sg_next(src_sg); + } else + dst_target = UINT_MAX; + + while (dst_target && dst_sg) { + if (sg_dma_len(dst_sg) & 0xf) + d = flexrm_dst_desc(sg_dma_address(dst_sg), + sg_dma_len(dst_sg)); + else + d = flexrm_mdst_desc(sg_dma_address(dst_sg), + sg_dma_len(dst_sg)/16); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + if (sg_dma_len(dst_sg) < dst_target) + dst_target -= sg_dma_len(dst_sg); + else + dst_target = 0; + dst_sg = sg_next(dst_sg); + } + } + + /* Null descriptor with invalid toggle bit */ + flexrm_write_desc(desc_ptr, flexrm_null_desc(!toggle)); + + /* Ensure that descriptors have been written to memory */ + wmb(); + + /* Flip toggle bit in header */ + flexrm_flip_header_toggle(orig_desc_ptr); + + return desc_ptr; +} + +static bool flexrm_sba_sanity_check(struct brcm_message *msg) +{ + u32 i; + + if (!msg->sba.cmds || !msg->sba.cmds_count) + return false; + + for (i = 0; i < msg->sba.cmds_count; i++) { + if (((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) || + (msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C)) && + (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT)) + return false; + if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) && + (msg->sba.cmds[i].data_len > SRCT_LENGTH_MASK)) + return false; + if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C) && + (msg->sba.cmds[i].data_len > SRCT_LENGTH_MASK)) + return false; + if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_RESP) && + (msg->sba.cmds[i].resp_len > DSTT_LENGTH_MASK)) + return false; + if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT) && + (msg->sba.cmds[i].data_len > DSTT_LENGTH_MASK)) + return false; + } + + return true; +} + +static u32 flexrm_sba_estimate_nonheader_desc_count(struct brcm_message *msg) +{ + u32 i, cnt; + + cnt = 0; + for (i = 0; i < msg->sba.cmds_count; i++) { + cnt++; + + if ((msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_B) || + (msg->sba.cmds[i].flags & BRCM_SBA_CMD_TYPE_C)) + cnt++; + + if (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_RESP) + cnt++; + + if (msg->sba.cmds[i].flags & BRCM_SBA_CMD_HAS_OUTPUT) + cnt++; + } + + return cnt; +} + +static void *flexrm_sba_write_descs(struct brcm_message *msg, u32 nhcnt, + u32 reqid, void *desc_ptr, u32 toggle, + void *start_desc, void *end_desc) +{ + u64 d; + u32 i, nhpos = 0; + struct brcm_sba_command *c; + void *orig_desc_ptr = desc_ptr; + + /* Convert SBA commands into descriptors */ + for (i = 0; i < msg->sba.cmds_count; i++) { + c = &msg->sba.cmds[i]; + + if ((c->flags & BRCM_SBA_CMD_HAS_RESP) && + (c->flags & BRCM_SBA_CMD_HAS_OUTPUT)) { + /* Destination response descriptor */ + d = flexrm_dst_desc(c->resp, c->resp_len); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } else if (c->flags & BRCM_SBA_CMD_HAS_RESP) { + /* Destination response with tlast descriptor */ + d = flexrm_dstt_desc(c->resp, c->resp_len); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } + + if (c->flags & BRCM_SBA_CMD_HAS_OUTPUT) { + /* Destination with tlast descriptor */ + d = flexrm_dstt_desc(c->data, c->data_len); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } + + if (c->flags & BRCM_SBA_CMD_TYPE_B) { + /* Command as immediate descriptor */ + d = flexrm_imm_desc(c->cmd); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } else { + /* Command as immediate descriptor with tlast */ + d = flexrm_immt_desc(c->cmd); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } + + if ((c->flags & BRCM_SBA_CMD_TYPE_B) || + (c->flags & BRCM_SBA_CMD_TYPE_C)) { + /* Source with tlast descriptor */ + d = flexrm_srct_desc(c->data, c->data_len); + flexrm_enqueue_desc(nhpos, nhcnt, reqid, + d, &desc_ptr, &toggle, + start_desc, end_desc); + nhpos++; + } + } + + /* Null descriptor with invalid toggle bit */ + flexrm_write_desc(desc_ptr, flexrm_null_desc(!toggle)); + + /* Ensure that descriptors have been written to memory */ + wmb(); + + /* Flip toggle bit in header */ + flexrm_flip_header_toggle(orig_desc_ptr); + + return desc_ptr; +} + +static bool flexrm_sanity_check(struct brcm_message *msg) +{ + if (!msg) + return false; + + switch (msg->type) { + case BRCM_MESSAGE_SPU: + return flexrm_spu_sanity_check(msg); + case BRCM_MESSAGE_SBA: + return flexrm_sba_sanity_check(msg); + default: + return false; + }; +} + +static u32 flexrm_estimate_nonheader_desc_count(struct brcm_message *msg) +{ + if (!msg) + return 0; + + switch (msg->type) { + case BRCM_MESSAGE_SPU: + return flexrm_spu_estimate_nonheader_desc_count(msg); + case BRCM_MESSAGE_SBA: + return flexrm_sba_estimate_nonheader_desc_count(msg); + default: + return 0; + }; +} + +static int flexrm_dma_map(struct device *dev, struct brcm_message *msg) +{ + if (!dev || !msg) + return -EINVAL; + + switch (msg->type) { + case BRCM_MESSAGE_SPU: + return flexrm_spu_dma_map(dev, msg); + default: + break; + } + + return 0; +} + +static void flexrm_dma_unmap(struct device *dev, struct brcm_message *msg) +{ + if (!dev || !msg) + return; + + switch (msg->type) { + case BRCM_MESSAGE_SPU: + flexrm_spu_dma_unmap(dev, msg); + break; + default: + break; + } +} + +static void *flexrm_write_descs(struct brcm_message *msg, u32 nhcnt, + u32 reqid, void *desc_ptr, u32 toggle, + void *start_desc, void *end_desc) +{ + if (!msg || !desc_ptr || !start_desc || !end_desc) + return ERR_PTR(-ENOTSUPP); + + if ((desc_ptr < start_desc) || (end_desc <= desc_ptr)) + return ERR_PTR(-ERANGE); + + switch (msg->type) { + case BRCM_MESSAGE_SPU: + return flexrm_spu_write_descs(msg, nhcnt, reqid, + desc_ptr, toggle, + start_desc, end_desc); + case BRCM_MESSAGE_SBA: + return flexrm_sba_write_descs(msg, nhcnt, reqid, + desc_ptr, toggle, + start_desc, end_desc); + default: + return ERR_PTR(-ENOTSUPP); + }; +} + +/* ====== FlexRM driver helper routines ===== */ + +static void flexrm_write_config_in_seqfile(struct flexrm_mbox *mbox, + struct seq_file *file) +{ + int i; + const char *state; + struct flexrm_ring *ring; + + seq_printf(file, "%-5s %-9s %-18s %-10s %-18s %-10s\n", + "Ring#", "State", "BD_Addr", "BD_Size", + "Cmpl_Addr", "Cmpl_Size"); + + for (i = 0; i < mbox->num_rings; i++) { + ring = &mbox->rings[i]; + if (readl(ring->regs + RING_CONTROL) & + BIT(CONTROL_ACTIVE_SHIFT)) + state = "active"; + else + state = "inactive"; + seq_printf(file, + "%-5d %-9s 0x%016llx 0x%08x 0x%016llx 0x%08x\n", + ring->num, state, + (unsigned long long)ring->bd_dma_base, + (u32)RING_BD_SIZE, + (unsigned long long)ring->cmpl_dma_base, + (u32)RING_CMPL_SIZE); + } +} + +static void flexrm_write_stats_in_seqfile(struct flexrm_mbox *mbox, + struct seq_file *file) +{ + int i; + u32 val, bd_read_offset; + struct flexrm_ring *ring; + + seq_printf(file, "%-5s %-10s %-10s %-10s %-11s %-11s\n", + "Ring#", "BD_Read", "BD_Write", + "Cmpl_Read", "Submitted", "Completed"); + + for (i = 0; i < mbox->num_rings; i++) { + ring = &mbox->rings[i]; + bd_read_offset = readl_relaxed(ring->regs + RING_BD_READ_PTR); + val = readl_relaxed(ring->regs + RING_BD_START_ADDR); + bd_read_offset *= RING_DESC_SIZE; + bd_read_offset += (u32)(BD_START_ADDR_DECODE(val) - + ring->bd_dma_base); + seq_printf(file, "%-5d 0x%08x 0x%08x 0x%08x %-11d %-11d\n", + ring->num, + (u32)bd_read_offset, + (u32)ring->bd_write_offset, + (u32)ring->cmpl_read_offset, + (u32)atomic_read(&ring->msg_send_count), + (u32)atomic_read(&ring->msg_cmpl_count)); + } +} + +static int flexrm_new_request(struct flexrm_ring *ring, + struct brcm_message *batch_msg, + struct brcm_message *msg) +{ + void *next; + unsigned long flags; + u32 val, count, nhcnt; + u32 read_offset, write_offset; + bool exit_cleanup = false; + int ret = 0, reqid; + + /* Do sanity check on message */ + if (!flexrm_sanity_check(msg)) + return -EIO; + msg->error = 0; + + /* If no requests possible then save data pointer and goto done. */ + spin_lock_irqsave(&ring->lock, flags); + reqid = bitmap_find_free_region(ring->requests_bmap, + RING_MAX_REQ_COUNT, 0); + spin_unlock_irqrestore(&ring->lock, flags); + if (reqid < 0) + return -ENOSPC; + ring->requests[reqid] = msg; + + /* Do DMA mappings for the message */ + ret = flexrm_dma_map(ring->mbox->dev, msg); + if (ret < 0) { + ring->requests[reqid] = NULL; + spin_lock_irqsave(&ring->lock, flags); + bitmap_release_region(ring->requests_bmap, reqid, 0); + spin_unlock_irqrestore(&ring->lock, flags); + return ret; + } + + /* Determine current HW BD read offset */ + read_offset = readl_relaxed(ring->regs + RING_BD_READ_PTR); + val = readl_relaxed(ring->regs + RING_BD_START_ADDR); + read_offset *= RING_DESC_SIZE; + read_offset += (u32)(BD_START_ADDR_DECODE(val) - ring->bd_dma_base); + + /* + * Number required descriptors = number of non-header descriptors + + * number of header descriptors + + * 1x null descriptor + */ + nhcnt = flexrm_estimate_nonheader_desc_count(msg); + count = flexrm_estimate_header_desc_count(nhcnt) + nhcnt + 1; + + /* Check for available descriptor space. */ + write_offset = ring->bd_write_offset; + while (count) { + if (!flexrm_is_next_table_desc(ring->bd_base + write_offset)) + count--; + write_offset += RING_DESC_SIZE; + if (write_offset == RING_BD_SIZE) + write_offset = 0x0; + if (write_offset == read_offset) + break; + } + if (count) { + ret = -ENOSPC; + exit_cleanup = true; + goto exit; + } + + /* Write descriptors to ring */ + next = flexrm_write_descs(msg, nhcnt, reqid, + ring->bd_base + ring->bd_write_offset, + RING_BD_TOGGLE_VALID(ring->bd_write_offset), + ring->bd_base, ring->bd_base + RING_BD_SIZE); + if (IS_ERR(next)) { + ret = PTR_ERR(next); + exit_cleanup = true; + goto exit; + } + + /* Save ring BD write offset */ + ring->bd_write_offset = (unsigned long)(next - ring->bd_base); + + /* Increment number of messages sent */ + atomic_inc_return(&ring->msg_send_count); + +exit: + /* Update error status in message */ + msg->error = ret; + + /* Cleanup if we failed */ + if (exit_cleanup) { + flexrm_dma_unmap(ring->mbox->dev, msg); + ring->requests[reqid] = NULL; + spin_lock_irqsave(&ring->lock, flags); + bitmap_release_region(ring->requests_bmap, reqid, 0); + spin_unlock_irqrestore(&ring->lock, flags); + } + + return ret; +} + +static int flexrm_process_completions(struct flexrm_ring *ring) +{ + u64 desc; + int err, count = 0; + unsigned long flags; + struct brcm_message *msg = NULL; + u32 reqid, cmpl_read_offset, cmpl_write_offset; + struct mbox_chan *chan = &ring->mbox->controller.chans[ring->num]; + + spin_lock_irqsave(&ring->lock, flags); + + /* + * Get current completion read and write offset + * + * Note: We should read completion write pointer at least once + * after we get a MSI interrupt because HW maintains internal + * MSI status which will allow next MSI interrupt only after + * completion write pointer is read. + */ + cmpl_write_offset = readl_relaxed(ring->regs + RING_CMPL_WRITE_PTR); + cmpl_write_offset *= RING_DESC_SIZE; + cmpl_read_offset = ring->cmpl_read_offset; + ring->cmpl_read_offset = cmpl_write_offset; + + spin_unlock_irqrestore(&ring->lock, flags); + + /* For each completed request notify mailbox clients */ + reqid = 0; + while (cmpl_read_offset != cmpl_write_offset) { + /* Dequeue next completion descriptor */ + desc = *((u64 *)(ring->cmpl_base + cmpl_read_offset)); + + /* Next read offset */ + cmpl_read_offset += RING_DESC_SIZE; + if (cmpl_read_offset == RING_CMPL_SIZE) + cmpl_read_offset = 0; + + /* Decode error from completion descriptor */ + err = flexrm_cmpl_desc_to_error(desc); + if (err < 0) { + dev_warn(ring->mbox->dev, + "ring%d got completion desc=0x%lx with error %d\n", + ring->num, (unsigned long)desc, err); + } + + /* Determine request id from completion descriptor */ + reqid = flexrm_cmpl_desc_to_reqid(desc); + + /* Determine message pointer based on reqid */ + msg = ring->requests[reqid]; + if (!msg) { + dev_warn(ring->mbox->dev, + "ring%d null msg pointer for completion desc=0x%lx\n", + ring->num, (unsigned long)desc); + continue; + } + + /* Release reqid for recycling */ + ring->requests[reqid] = NULL; + spin_lock_irqsave(&ring->lock, flags); + bitmap_release_region(ring->requests_bmap, reqid, 0); + spin_unlock_irqrestore(&ring->lock, flags); + + /* Unmap DMA mappings */ + flexrm_dma_unmap(ring->mbox->dev, msg); + + /* Give-back message to mailbox client */ + msg->error = err; + mbox_chan_received_data(chan, msg); + + /* Increment number of completions processed */ + atomic_inc_return(&ring->msg_cmpl_count); + count++; + } + + return count; +} + +/* ====== FlexRM Debugfs callbacks ====== */ + +static int flexrm_debugfs_conf_show(struct seq_file *file, void *offset) +{ + struct flexrm_mbox *mbox = dev_get_drvdata(file->private); + + /* Write config in file */ + flexrm_write_config_in_seqfile(mbox, file); + + return 0; +} + +static int flexrm_debugfs_stats_show(struct seq_file *file, void *offset) +{ + struct flexrm_mbox *mbox = dev_get_drvdata(file->private); + + /* Write stats in file */ + flexrm_write_stats_in_seqfile(mbox, file); + + return 0; +} + +/* ====== FlexRM interrupt handler ===== */ + +static irqreturn_t flexrm_irq_thread(int irq, void *dev_id) +{ + flexrm_process_completions(dev_id); + + return IRQ_HANDLED; +} + +/* ====== FlexRM mailbox callbacks ===== */ + +static int flexrm_send_data(struct mbox_chan *chan, void *data) +{ + int i, rc; + struct flexrm_ring *ring = chan->con_priv; + struct brcm_message *msg = data; + + if (msg->type == BRCM_MESSAGE_BATCH) { + for (i = msg->batch.msgs_queued; + i < msg->batch.msgs_count; i++) { + rc = flexrm_new_request(ring, msg, + &msg->batch.msgs[i]); + if (rc) { + msg->error = rc; + return rc; + } + msg->batch.msgs_queued++; + } + return 0; + } + + return flexrm_new_request(ring, NULL, data); +} + +static bool flexrm_peek_data(struct mbox_chan *chan) +{ + int cnt = flexrm_process_completions(chan->con_priv); + + return (cnt > 0) ? true : false; +} + +static int flexrm_startup(struct mbox_chan *chan) +{ + u64 d; + u32 val, off; + int ret = 0; + dma_addr_t next_addr; + struct flexrm_ring *ring = chan->con_priv; + + /* Allocate BD memory */ + ring->bd_base = dma_pool_alloc(ring->mbox->bd_pool, + GFP_KERNEL, &ring->bd_dma_base); + if (!ring->bd_base) { + dev_err(ring->mbox->dev, + "can't allocate BD memory for ring%d\n", + ring->num); + ret = -ENOMEM; + goto fail; + } + + /* Configure next table pointer entries in BD memory */ + for (off = 0; off < RING_BD_SIZE; off += RING_DESC_SIZE) { + next_addr = off + RING_DESC_SIZE; + if (next_addr == RING_BD_SIZE) + next_addr = 0; + next_addr += ring->bd_dma_base; + if (RING_BD_ALIGN_CHECK(next_addr)) + d = flexrm_next_table_desc(RING_BD_TOGGLE_VALID(off), + next_addr); + else + d = flexrm_null_desc(RING_BD_TOGGLE_INVALID(off)); + flexrm_write_desc(ring->bd_base + off, d); + } + + /* Allocate completion memory */ + ring->cmpl_base = dma_pool_zalloc(ring->mbox->cmpl_pool, + GFP_KERNEL, &ring->cmpl_dma_base); + if (!ring->cmpl_base) { + dev_err(ring->mbox->dev, + "can't allocate completion memory for ring%d\n", + ring->num); + ret = -ENOMEM; + goto fail_free_bd_memory; + } + + /* Request IRQ */ + if (ring->irq == UINT_MAX) { + dev_err(ring->mbox->dev, + "ring%d IRQ not available\n", ring->num); + ret = -ENODEV; + goto fail_free_cmpl_memory; + } + ret = request_threaded_irq(ring->irq, NULL, flexrm_irq_thread, + IRQF_ONESHOT, dev_name(ring->mbox->dev), ring); + if (ret) { + dev_err(ring->mbox->dev, + "failed to request ring%d IRQ\n", ring->num); + goto fail_free_cmpl_memory; + } + ring->irq_requested = true; + + /* Set IRQ affinity hint */ + ring->irq_aff_hint = CPU_MASK_NONE; + val = ring->mbox->num_rings; + val = (num_online_cpus() < val) ? val / num_online_cpus() : 1; + cpumask_set_cpu((ring->num / val) % num_online_cpus(), + &ring->irq_aff_hint); + ret = irq_update_affinity_hint(ring->irq, &ring->irq_aff_hint); + if (ret) { + dev_err(ring->mbox->dev, + "failed to set IRQ affinity hint for ring%d\n", + ring->num); + goto fail_free_irq; + } + + /* Disable/inactivate ring */ + writel_relaxed(0x0, ring->regs + RING_CONTROL); + + /* Program BD start address */ + val = BD_START_ADDR_VALUE(ring->bd_dma_base); + writel_relaxed(val, ring->regs + RING_BD_START_ADDR); + + /* BD write pointer will be same as HW write pointer */ + ring->bd_write_offset = + readl_relaxed(ring->regs + RING_BD_WRITE_PTR); + ring->bd_write_offset *= RING_DESC_SIZE; + + /* Program completion start address */ + val = CMPL_START_ADDR_VALUE(ring->cmpl_dma_base); + writel_relaxed(val, ring->regs + RING_CMPL_START_ADDR); + + /* Completion read pointer will be same as HW write pointer */ + ring->cmpl_read_offset = + readl_relaxed(ring->regs + RING_CMPL_WRITE_PTR); + ring->cmpl_read_offset *= RING_DESC_SIZE; + + /* Read ring Tx, Rx, and Outstanding counts to clear */ + readl_relaxed(ring->regs + RING_NUM_REQ_RECV_LS); + readl_relaxed(ring->regs + RING_NUM_REQ_RECV_MS); + readl_relaxed(ring->regs + RING_NUM_REQ_TRANS_LS); + readl_relaxed(ring->regs + RING_NUM_REQ_TRANS_MS); + readl_relaxed(ring->regs + RING_NUM_REQ_OUTSTAND); + + /* Configure RING_MSI_CONTROL */ + val = 0; + val |= (ring->msi_timer_val << MSI_TIMER_VAL_SHIFT); + val |= BIT(MSI_ENABLE_SHIFT); + val |= (ring->msi_count_threshold & MSI_COUNT_MASK) << MSI_COUNT_SHIFT; + writel_relaxed(val, ring->regs + RING_MSI_CONTROL); + + /* Enable/activate ring */ + val = BIT(CONTROL_ACTIVE_SHIFT); + writel_relaxed(val, ring->regs + RING_CONTROL); + + /* Reset stats to zero */ + atomic_set(&ring->msg_send_count, 0); + atomic_set(&ring->msg_cmpl_count, 0); + + return 0; + +fail_free_irq: + free_irq(ring->irq, ring); + ring->irq_requested = false; +fail_free_cmpl_memory: + dma_pool_free(ring->mbox->cmpl_pool, + ring->cmpl_base, ring->cmpl_dma_base); + ring->cmpl_base = NULL; +fail_free_bd_memory: + dma_pool_free(ring->mbox->bd_pool, + ring->bd_base, ring->bd_dma_base); + ring->bd_base = NULL; +fail: + return ret; +} + +static void flexrm_shutdown(struct mbox_chan *chan) +{ + u32 reqid; + unsigned int timeout; + struct brcm_message *msg; + struct flexrm_ring *ring = chan->con_priv; + + /* Disable/inactivate ring */ + writel_relaxed(0x0, ring->regs + RING_CONTROL); + + /* Set ring flush state */ + timeout = 1000; /* timeout of 1s */ + writel_relaxed(BIT(CONTROL_FLUSH_SHIFT), + ring->regs + RING_CONTROL); + do { + if (readl_relaxed(ring->regs + RING_FLUSH_DONE) & + FLUSH_DONE_MASK) + break; + mdelay(1); + } while (--timeout); + if (!timeout) + dev_err(ring->mbox->dev, + "setting ring%d flush state timedout\n", ring->num); + + /* Clear ring flush state */ + timeout = 1000; /* timeout of 1s */ + writel_relaxed(0x0, ring->regs + RING_CONTROL); + do { + if (!(readl_relaxed(ring->regs + RING_FLUSH_DONE) & + FLUSH_DONE_MASK)) + break; + mdelay(1); + } while (--timeout); + if (!timeout) + dev_err(ring->mbox->dev, + "clearing ring%d flush state timedout\n", ring->num); + + /* Abort all in-flight requests */ + for (reqid = 0; reqid < RING_MAX_REQ_COUNT; reqid++) { + msg = ring->requests[reqid]; + if (!msg) + continue; + + /* Release reqid for recycling */ + ring->requests[reqid] = NULL; + + /* Unmap DMA mappings */ + flexrm_dma_unmap(ring->mbox->dev, msg); + + /* Give-back message to mailbox client */ + msg->error = -EIO; + mbox_chan_received_data(chan, msg); + } + + /* Clear requests bitmap */ + bitmap_zero(ring->requests_bmap, RING_MAX_REQ_COUNT); + + /* Release IRQ */ + if (ring->irq_requested) { + irq_update_affinity_hint(ring->irq, NULL); + free_irq(ring->irq, ring); + ring->irq_requested = false; + } + + /* Free-up completion descriptor ring */ + if (ring->cmpl_base) { + dma_pool_free(ring->mbox->cmpl_pool, + ring->cmpl_base, ring->cmpl_dma_base); + ring->cmpl_base = NULL; + } + + /* Free-up BD descriptor ring */ + if (ring->bd_base) { + dma_pool_free(ring->mbox->bd_pool, + ring->bd_base, ring->bd_dma_base); + ring->bd_base = NULL; + } +} + +static const struct mbox_chan_ops flexrm_mbox_chan_ops = { + .send_data = flexrm_send_data, + .startup = flexrm_startup, + .shutdown = flexrm_shutdown, + .peek_data = flexrm_peek_data, +}; + +static struct mbox_chan *flexrm_mbox_of_xlate(struct mbox_controller *cntlr, + const struct of_phandle_args *pa) +{ + struct mbox_chan *chan; + struct flexrm_ring *ring; + + if (pa->args_count < 3) + return ERR_PTR(-EINVAL); + + if (pa->args[0] >= cntlr->num_chans) + return ERR_PTR(-ENOENT); + + if (pa->args[1] > MSI_COUNT_MASK) + return ERR_PTR(-EINVAL); + + if (pa->args[2] > MSI_TIMER_VAL_MASK) + return ERR_PTR(-EINVAL); + + chan = &cntlr->chans[pa->args[0]]; + ring = chan->con_priv; + ring->msi_count_threshold = pa->args[1]; + ring->msi_timer_val = pa->args[2]; + + return chan; +} + +/* ====== FlexRM platform driver ===== */ + +static void flexrm_mbox_msi_write(struct msi_desc *desc, struct msi_msg *msg) +{ + struct device *dev = msi_desc_to_dev(desc); + struct flexrm_mbox *mbox = dev_get_drvdata(dev); + struct flexrm_ring *ring = &mbox->rings[desc->msi_index]; + + /* Configure per-Ring MSI registers */ + writel_relaxed(msg->address_lo, ring->regs + RING_MSI_ADDR_LS); + writel_relaxed(msg->address_hi, ring->regs + RING_MSI_ADDR_MS); + writel_relaxed(msg->data, ring->regs + RING_MSI_DATA_VALUE); +} + +static int flexrm_mbox_probe(struct platform_device *pdev) +{ + int index, ret = 0; + void __iomem *regs; + void __iomem *regs_end; + struct resource *iomem; + struct flexrm_ring *ring; + struct flexrm_mbox *mbox; + struct device *dev = &pdev->dev; + + /* Allocate driver mailbox struct */ + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) { + ret = -ENOMEM; + goto fail; + } + mbox->dev = dev; + platform_set_drvdata(pdev, mbox); + + /* Get resource for registers and map registers of all rings */ + mbox->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem); + if (!iomem || (resource_size(iomem) < RING_REGS_SIZE)) { + ret = -ENODEV; + goto fail; + } else if (IS_ERR(mbox->regs)) { + ret = PTR_ERR(mbox->regs); + goto fail; + } + regs_end = mbox->regs + resource_size(iomem); + + /* Scan and count available rings */ + mbox->num_rings = 0; + for (regs = mbox->regs; regs < regs_end; regs += RING_REGS_SIZE) { + if (readl_relaxed(regs + RING_VER) == RING_VER_MAGIC) + mbox->num_rings++; + } + if (!mbox->num_rings) { + ret = -ENODEV; + goto fail; + } + + /* Allocate driver ring structs */ + ring = devm_kcalloc(dev, mbox->num_rings, sizeof(*ring), GFP_KERNEL); + if (!ring) { + ret = -ENOMEM; + goto fail; + } + mbox->rings = ring; + + /* Initialize members of driver ring structs */ + regs = mbox->regs; + for (index = 0; index < mbox->num_rings; index++) { + ring = &mbox->rings[index]; + ring->num = index; + ring->mbox = mbox; + while ((regs < regs_end) && + (readl_relaxed(regs + RING_VER) != RING_VER_MAGIC)) + regs += RING_REGS_SIZE; + if (regs_end <= regs) { + ret = -ENODEV; + goto fail; + } + ring->regs = regs; + regs += RING_REGS_SIZE; + ring->irq = UINT_MAX; + ring->irq_requested = false; + ring->msi_timer_val = MSI_TIMER_VAL_MASK; + ring->msi_count_threshold = 0x1; + memset(ring->requests, 0, sizeof(ring->requests)); + ring->bd_base = NULL; + ring->bd_dma_base = 0; + ring->cmpl_base = NULL; + ring->cmpl_dma_base = 0; + atomic_set(&ring->msg_send_count, 0); + atomic_set(&ring->msg_cmpl_count, 0); + spin_lock_init(&ring->lock); + bitmap_zero(ring->requests_bmap, RING_MAX_REQ_COUNT); + ring->cmpl_read_offset = 0; + } + + /* FlexRM is capable of 40-bit physical addresses only */ + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40)); + if (ret) { + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + goto fail; + } + + /* Create DMA pool for ring BD memory */ + mbox->bd_pool = dma_pool_create("bd", dev, RING_BD_SIZE, + 1 << RING_BD_ALIGN_ORDER, 0); + if (!mbox->bd_pool) { + ret = -ENOMEM; + goto fail; + } + + /* Create DMA pool for ring completion memory */ + mbox->cmpl_pool = dma_pool_create("cmpl", dev, RING_CMPL_SIZE, + 1 << RING_CMPL_ALIGN_ORDER, 0); + if (!mbox->cmpl_pool) { + ret = -ENOMEM; + goto fail_destroy_bd_pool; + } + + /* Allocate platform MSIs for each ring */ + ret = platform_device_msi_init_and_alloc_irqs(dev, mbox->num_rings, + flexrm_mbox_msi_write); + if (ret) + goto fail_destroy_cmpl_pool; + + /* Save alloced IRQ numbers for each ring */ + for (index = 0; index < mbox->num_rings; index++) + mbox->rings[index].irq = msi_get_virq(dev, index); + + /* Check availability of debugfs */ + if (!debugfs_initialized()) + goto skip_debugfs; + + /* Create debugfs root entry */ + mbox->root = debugfs_create_dir(dev_name(mbox->dev), NULL); + + /* Create debugfs config entry */ + debugfs_create_devm_seqfile(mbox->dev, "config", mbox->root, + flexrm_debugfs_conf_show); + + /* Create debugfs stats entry */ + debugfs_create_devm_seqfile(mbox->dev, "stats", mbox->root, + flexrm_debugfs_stats_show); + +skip_debugfs: + + /* Initialize mailbox controller */ + mbox->controller.txdone_irq = false; + mbox->controller.txdone_poll = false; + mbox->controller.ops = &flexrm_mbox_chan_ops; + mbox->controller.dev = dev; + mbox->controller.num_chans = mbox->num_rings; + mbox->controller.of_xlate = flexrm_mbox_of_xlate; + mbox->controller.chans = devm_kcalloc(dev, mbox->num_rings, + sizeof(*mbox->controller.chans), GFP_KERNEL); + if (!mbox->controller.chans) { + ret = -ENOMEM; + goto fail_free_debugfs_root; + } + for (index = 0; index < mbox->num_rings; index++) + mbox->controller.chans[index].con_priv = &mbox->rings[index]; + + /* Register mailbox controller */ + ret = devm_mbox_controller_register(dev, &mbox->controller); + if (ret) + goto fail_free_debugfs_root; + + dev_info(dev, "registered flexrm mailbox with %d channels\n", + mbox->controller.num_chans); + + return 0; + +fail_free_debugfs_root: + debugfs_remove_recursive(mbox->root); + platform_device_msi_free_irqs_all(dev); +fail_destroy_cmpl_pool: + dma_pool_destroy(mbox->cmpl_pool); +fail_destroy_bd_pool: + dma_pool_destroy(mbox->bd_pool); +fail: + return ret; +} + +static void flexrm_mbox_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct flexrm_mbox *mbox = platform_get_drvdata(pdev); + + debugfs_remove_recursive(mbox->root); + + platform_device_msi_free_irqs_all(dev); + + dma_pool_destroy(mbox->cmpl_pool); + dma_pool_destroy(mbox->bd_pool); +} + +static const struct of_device_id flexrm_mbox_of_match[] = { + { .compatible = "brcm,iproc-flexrm-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, flexrm_mbox_of_match); + +static struct platform_driver flexrm_mbox_driver = { + .driver = { + .name = "brcm-flexrm-mbox", + .of_match_table = flexrm_mbox_of_match, + }, + .probe = flexrm_mbox_probe, + .remove = flexrm_mbox_remove, +}; +module_platform_driver(flexrm_mbox_driver); + +MODULE_AUTHOR("Anup Patel <anup.patel@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom FlexRM mailbox driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/bcm-pdc-mailbox.c b/drivers/mailbox/bcm-pdc-mailbox.c new file mode 100644 index 000000000..6fcc4002a --- /dev/null +++ b/drivers/mailbox/bcm-pdc-mailbox.c @@ -0,0 +1,1628 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 Broadcom + */ + +/* + * Broadcom PDC Mailbox Driver + * The PDC provides a ring based programming interface to one or more hardware + * offload engines. For example, the PDC driver works with both SPU-M and SPU2 + * cryptographic offload hardware. In some chips the PDC is referred to as MDE, + * and in others the FA2/FA+ hardware is used with this PDC driver. + * + * The PDC driver registers with the Linux mailbox framework as a mailbox + * controller, once for each PDC instance. Ring 0 for each PDC is registered as + * a mailbox channel. The PDC driver uses interrupts to determine when data + * transfers to and from an offload engine are complete. The PDC driver uses + * threaded IRQs so that response messages are handled outside of interrupt + * context. + * + * The PDC driver allows multiple messages to be pending in the descriptor + * rings. The tx_msg_start descriptor index indicates where the last message + * starts. The txin_numd value at this index indicates how many descriptor + * indexes make up the message. Similar state is kept on the receive side. When + * an rx interrupt indicates a response is ready, the PDC driver processes numd + * descriptors from the tx and rx ring, thus processing one response at a time. + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/brcm-message.h> +#include <linux/scatterlist.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/workqueue.h> + +#define PDC_SUCCESS 0 + +#define RING_ENTRY_SIZE sizeof(struct dma64dd) + +/* # entries in PDC dma ring */ +#define PDC_RING_ENTRIES 512 +/* + * Minimum number of ring descriptor entries that must be free to tell mailbox + * framework that it can submit another request + */ +#define PDC_RING_SPACE_MIN 15 + +#define PDC_RING_SIZE (PDC_RING_ENTRIES * RING_ENTRY_SIZE) +/* Rings are 8k aligned */ +#define RING_ALIGN_ORDER 13 +#define RING_ALIGN BIT(RING_ALIGN_ORDER) + +#define RX_BUF_ALIGN_ORDER 5 +#define RX_BUF_ALIGN BIT(RX_BUF_ALIGN_ORDER) + +/* descriptor bumping macros */ +#define XXD(x, max_mask) ((x) & (max_mask)) +#define TXD(x, max_mask) XXD((x), (max_mask)) +#define RXD(x, max_mask) XXD((x), (max_mask)) +#define NEXTTXD(i, max_mask) TXD((i) + 1, (max_mask)) +#define PREVTXD(i, max_mask) TXD((i) - 1, (max_mask)) +#define NEXTRXD(i, max_mask) RXD((i) + 1, (max_mask)) +#define PREVRXD(i, max_mask) RXD((i) - 1, (max_mask)) +#define NTXDACTIVE(h, t, max_mask) TXD((t) - (h), (max_mask)) +#define NRXDACTIVE(h, t, max_mask) RXD((t) - (h), (max_mask)) + +/* Length of BCM header at start of SPU msg, in bytes */ +#define BCM_HDR_LEN 8 + +/* + * PDC driver reserves ringset 0 on each SPU for its own use. The driver does + * not currently support use of multiple ringsets on a single PDC engine. + */ +#define PDC_RINGSET 0 + +/* + * Interrupt mask and status definitions. Enable interrupts for tx and rx on + * ring 0 + */ +#define PDC_RCVINT_0 (16 + PDC_RINGSET) +#define PDC_RCVINTEN_0 BIT(PDC_RCVINT_0) +#define PDC_INTMASK (PDC_RCVINTEN_0) +#define PDC_LAZY_FRAMECOUNT 1 +#define PDC_LAZY_TIMEOUT 10000 +#define PDC_LAZY_INT (PDC_LAZY_TIMEOUT | (PDC_LAZY_FRAMECOUNT << 24)) +#define PDC_INTMASK_OFFSET 0x24 +#define PDC_INTSTATUS_OFFSET 0x20 +#define PDC_RCVLAZY0_OFFSET (0x30 + 4 * PDC_RINGSET) +#define FA_RCVLAZY0_OFFSET 0x100 + +/* + * For SPU2, configure MDE_CKSUM_CONTROL to write 17 bytes of metadata + * before frame + */ +#define PDC_SPU2_RESP_HDR_LEN 17 +#define PDC_CKSUM_CTRL BIT(27) +#define PDC_CKSUM_CTRL_OFFSET 0x400 + +#define PDC_SPUM_RESP_HDR_LEN 32 + +/* + * Sets the following bits for write to transmit control reg: + * 11 - PtyChkDisable - parity check is disabled + * 20:18 - BurstLen = 3 -> 2^7 = 128 byte data reads from memory + */ +#define PDC_TX_CTL 0x000C0800 + +/* Bit in tx control reg to enable tx channel */ +#define PDC_TX_ENABLE 0x1 + +/* + * Sets the following bits for write to receive control reg: + * 7:1 - RcvOffset - size in bytes of status region at start of rx frame buf + * 9 - SepRxHdrDescEn - place start of new frames only in descriptors + * that have StartOfFrame set + * 10 - OflowContinue - on rx FIFO overflow, clear rx fifo, discard all + * remaining bytes in current frame, report error + * in rx frame status for current frame + * 11 - PtyChkDisable - parity check is disabled + * 20:18 - BurstLen = 3 -> 2^7 = 128 byte data reads from memory + */ +#define PDC_RX_CTL 0x000C0E00 + +/* Bit in rx control reg to enable rx channel */ +#define PDC_RX_ENABLE 0x1 + +#define CRYPTO_D64_RS0_CD_MASK ((PDC_RING_ENTRIES * RING_ENTRY_SIZE) - 1) + +/* descriptor flags */ +#define D64_CTRL1_EOT BIT(28) /* end of descriptor table */ +#define D64_CTRL1_IOC BIT(29) /* interrupt on complete */ +#define D64_CTRL1_EOF BIT(30) /* end of frame */ +#define D64_CTRL1_SOF BIT(31) /* start of frame */ + +#define RX_STATUS_OVERFLOW 0x00800000 +#define RX_STATUS_LEN 0x0000FFFF + +#define PDC_TXREGS_OFFSET 0x200 +#define PDC_RXREGS_OFFSET 0x220 + +/* Maximum size buffer the DMA engine can handle */ +#define PDC_DMA_BUF_MAX 16384 + +enum pdc_hw { + FA_HW, /* FA2/FA+ hardware (i.e. Northstar Plus) */ + PDC_HW /* PDC/MDE hardware (i.e. Northstar 2, Pegasus) */ +}; + +/* dma descriptor */ +struct dma64dd { + u32 ctrl1; /* misc control bits */ + u32 ctrl2; /* buffer count and address extension */ + u32 addrlow; /* memory address of the date buffer, bits 31:0 */ + u32 addrhigh; /* memory address of the date buffer, bits 63:32 */ +}; + +/* dma registers per channel(xmt or rcv) */ +struct dma64_regs { + u32 control; /* enable, et al */ + u32 ptr; /* last descriptor posted to chip */ + u32 addrlow; /* descriptor ring base address low 32-bits */ + u32 addrhigh; /* descriptor ring base address bits 63:32 */ + u32 status0; /* last rx descriptor written by hw */ + u32 status1; /* driver does not use */ +}; + +/* cpp contortions to concatenate w/arg prescan */ +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif /* PAD */ + +/* dma registers. matches hw layout. */ +struct dma64 { + struct dma64_regs dmaxmt; /* dma tx */ + u32 PAD[2]; + struct dma64_regs dmarcv; /* dma rx */ + u32 PAD[2]; +}; + +/* PDC registers */ +struct pdc_regs { + u32 devcontrol; /* 0x000 */ + u32 devstatus; /* 0x004 */ + u32 PAD; + u32 biststatus; /* 0x00c */ + u32 PAD[4]; + u32 intstatus; /* 0x020 */ + u32 intmask; /* 0x024 */ + u32 gptimer; /* 0x028 */ + + u32 PAD; + u32 intrcvlazy_0; /* 0x030 (Only in PDC, not FA2) */ + u32 intrcvlazy_1; /* 0x034 (Only in PDC, not FA2) */ + u32 intrcvlazy_2; /* 0x038 (Only in PDC, not FA2) */ + u32 intrcvlazy_3; /* 0x03c (Only in PDC, not FA2) */ + + u32 PAD[48]; + u32 fa_intrecvlazy; /* 0x100 (Only in FA2, not PDC) */ + u32 flowctlthresh; /* 0x104 */ + u32 wrrthresh; /* 0x108 */ + u32 gmac_idle_cnt_thresh; /* 0x10c */ + + u32 PAD[4]; + u32 ifioaccessaddr; /* 0x120 */ + u32 ifioaccessbyte; /* 0x124 */ + u32 ifioaccessdata; /* 0x128 */ + + u32 PAD[21]; + u32 phyaccess; /* 0x180 */ + u32 PAD; + u32 phycontrol; /* 0x188 */ + u32 txqctl; /* 0x18c */ + u32 rxqctl; /* 0x190 */ + u32 gpioselect; /* 0x194 */ + u32 gpio_output_en; /* 0x198 */ + u32 PAD; /* 0x19c */ + u32 txq_rxq_mem_ctl; /* 0x1a0 */ + u32 memory_ecc_status; /* 0x1a4 */ + u32 serdes_ctl; /* 0x1a8 */ + u32 serdes_status0; /* 0x1ac */ + u32 serdes_status1; /* 0x1b0 */ + u32 PAD[11]; /* 0x1b4-1dc */ + u32 clk_ctl_st; /* 0x1e0 */ + u32 hw_war; /* 0x1e4 (Only in PDC, not FA2) */ + u32 pwrctl; /* 0x1e8 */ + u32 PAD[5]; + +#define PDC_NUM_DMA_RINGS 4 + struct dma64 dmaregs[PDC_NUM_DMA_RINGS]; /* 0x0200 - 0x2fc */ + + /* more registers follow, but we don't use them */ +}; + +/* structure for allocating/freeing DMA rings */ +struct pdc_ring_alloc { + dma_addr_t dmabase; /* DMA address of start of ring */ + void *vbase; /* base kernel virtual address of ring */ + u32 size; /* ring allocation size in bytes */ +}; + +/* + * context associated with a receive descriptor. + * @rxp_ctx: opaque context associated with frame that starts at each + * rx ring index. + * @dst_sg: Scatterlist used to form reply frames beginning at a given ring + * index. Retained in order to unmap each sg after reply is processed. + * @rxin_numd: Number of rx descriptors associated with the message that starts + * at a descriptor index. Not set for every index. For example, + * if descriptor index i points to a scatterlist with 4 entries, + * then the next three descriptor indexes don't have a value set. + * @resp_hdr: Virtual address of buffer used to catch DMA rx status + * @resp_hdr_daddr: physical address of DMA rx status buffer + */ +struct pdc_rx_ctx { + void *rxp_ctx; + struct scatterlist *dst_sg; + u32 rxin_numd; + void *resp_hdr; + dma_addr_t resp_hdr_daddr; +}; + +/* PDC state structure */ +struct pdc_state { + /* Index of the PDC whose state is in this structure instance */ + u8 pdc_idx; + + /* Platform device for this PDC instance */ + struct platform_device *pdev; + + /* + * Each PDC instance has a mailbox controller. PDC receives request + * messages through mailboxes, and sends response messages through the + * mailbox framework. + */ + struct mbox_controller mbc; + + unsigned int pdc_irq; + + /* work for deferred processing after DMA rx interrupt */ + struct work_struct rx_work; + + /* Number of bytes of receive status prior to each rx frame */ + u32 rx_status_len; + /* Whether a BCM header is prepended to each frame */ + bool use_bcm_hdr; + /* Sum of length of BCM header and rx status header */ + u32 pdc_resp_hdr_len; + + /* The base virtual address of DMA hw registers */ + void __iomem *pdc_reg_vbase; + + /* Pool for allocation of DMA rings */ + struct dma_pool *ring_pool; + + /* Pool for allocation of metadata buffers for response messages */ + struct dma_pool *rx_buf_pool; + + /* + * The base virtual address of DMA tx/rx descriptor rings. Corresponding + * DMA address and size of ring allocation. + */ + struct pdc_ring_alloc tx_ring_alloc; + struct pdc_ring_alloc rx_ring_alloc; + + struct pdc_regs *regs; /* start of PDC registers */ + + struct dma64_regs *txregs_64; /* dma tx engine registers */ + struct dma64_regs *rxregs_64; /* dma rx engine registers */ + + /* + * Arrays of PDC_RING_ENTRIES descriptors + * To use multiple ringsets, this needs to be extended + */ + struct dma64dd *txd_64; /* tx descriptor ring */ + struct dma64dd *rxd_64; /* rx descriptor ring */ + + /* descriptor ring sizes */ + u32 ntxd; /* # tx descriptors */ + u32 nrxd; /* # rx descriptors */ + u32 nrxpost; /* # rx buffers to keep posted */ + u32 ntxpost; /* max number of tx buffers that can be posted */ + + /* + * Index of next tx descriptor to reclaim. That is, the descriptor + * index of the oldest tx buffer for which the host has yet to process + * the corresponding response. + */ + u32 txin; + + /* + * Index of the first receive descriptor for the sequence of + * message fragments currently under construction. Used to build up + * the rxin_numd count for a message. Updated to rxout when the host + * starts a new sequence of rx buffers for a new message. + */ + u32 tx_msg_start; + + /* Index of next tx descriptor to post. */ + u32 txout; + + /* + * Number of tx descriptors associated with the message that starts + * at this tx descriptor index. + */ + u32 txin_numd[PDC_RING_ENTRIES]; + + /* + * Index of next rx descriptor to reclaim. This is the index of + * the next descriptor whose data has yet to be processed by the host. + */ + u32 rxin; + + /* + * Index of the first receive descriptor for the sequence of + * message fragments currently under construction. Used to build up + * the rxin_numd count for a message. Updated to rxout when the host + * starts a new sequence of rx buffers for a new message. + */ + u32 rx_msg_start; + + /* + * Saved value of current hardware rx descriptor index. + * The last rx buffer written by the hw is the index previous to + * this one. + */ + u32 last_rx_curr; + + /* Index of next rx descriptor to post. */ + u32 rxout; + + struct pdc_rx_ctx rx_ctx[PDC_RING_ENTRIES]; + + /* + * Scatterlists used to form request and reply frames beginning at a + * given ring index. Retained in order to unmap each sg after reply + * is processed + */ + struct scatterlist *src_sg[PDC_RING_ENTRIES]; + + /* counters */ + u32 pdc_requests; /* number of request messages submitted */ + u32 pdc_replies; /* number of reply messages received */ + u32 last_tx_not_done; /* too few tx descriptors to indicate done */ + u32 tx_ring_full; /* unable to accept msg because tx ring full */ + u32 rx_ring_full; /* unable to accept msg because rx ring full */ + u32 txnobuf; /* unable to create tx descriptor */ + u32 rxnobuf; /* unable to create rx descriptor */ + u32 rx_oflow; /* count of rx overflows */ + + /* hardware type - FA2 or PDC/MDE */ + enum pdc_hw hw_type; +}; + +/* Global variables */ + +struct pdc_globals { + /* Actual number of SPUs in hardware, as reported by device tree */ + u32 num_spu; +}; + +static struct pdc_globals pdcg; + +/* top level debug FS directory for PDC driver */ +static struct dentry *debugfs_dir; + +static ssize_t pdc_debugfs_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct pdc_state *pdcs; + char *buf; + ssize_t ret, out_offset, out_count; + + out_count = 512; + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pdcs = filp->private_data; + out_offset = 0; + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "SPU %u stats:\n", pdcs->pdc_idx); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "PDC requests....................%u\n", + pdcs->pdc_requests); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "PDC responses...................%u\n", + pdcs->pdc_replies); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Tx not done.....................%u\n", + pdcs->last_tx_not_done); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Tx ring full....................%u\n", + pdcs->tx_ring_full); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Rx ring full....................%u\n", + pdcs->rx_ring_full); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Tx desc write fail. Ring full...%u\n", + pdcs->txnobuf); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Rx desc write fail. Ring full...%u\n", + pdcs->rxnobuf); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Receive overflow................%u\n", + pdcs->rx_oflow); + out_offset += scnprintf(buf + out_offset, out_count - out_offset, + "Num frags in rx ring............%u\n", + NRXDACTIVE(pdcs->rxin, pdcs->last_rx_curr, + pdcs->nrxpost)); + + if (out_offset > out_count) + out_offset = out_count; + + ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); + kfree(buf); + return ret; +} + +static const struct file_operations pdc_debugfs_stats = { + .owner = THIS_MODULE, + .open = simple_open, + .read = pdc_debugfs_read, +}; + +/** + * pdc_setup_debugfs() - Create the debug FS directories. If the top-level + * directory has not yet been created, create it now. Create a stats file in + * this directory for a SPU. + * @pdcs: PDC state structure + */ +static void pdc_setup_debugfs(struct pdc_state *pdcs) +{ + char spu_stats_name[16]; + + if (!debugfs_initialized()) + return; + + snprintf(spu_stats_name, 16, "pdc%d_stats", pdcs->pdc_idx); + if (!debugfs_dir) + debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); + + /* S_IRUSR == 0400 */ + debugfs_create_file(spu_stats_name, 0400, debugfs_dir, pdcs, + &pdc_debugfs_stats); +} + +static void pdc_free_debugfs(void) +{ + debugfs_remove_recursive(debugfs_dir); + debugfs_dir = NULL; +} + +/** + * pdc_build_rxd() - Build DMA descriptor to receive SPU result. + * @pdcs: PDC state for SPU that will generate result + * @dma_addr: DMA address of buffer that descriptor is being built for + * @buf_len: Length of the receive buffer, in bytes + * @flags: Flags to be stored in descriptor + */ +static inline void +pdc_build_rxd(struct pdc_state *pdcs, dma_addr_t dma_addr, + u32 buf_len, u32 flags) +{ + struct device *dev = &pdcs->pdev->dev; + struct dma64dd *rxd = &pdcs->rxd_64[pdcs->rxout]; + + dev_dbg(dev, + "Writing rx descriptor for PDC %u at index %u with length %u. flags %#x\n", + pdcs->pdc_idx, pdcs->rxout, buf_len, flags); + + rxd->addrlow = cpu_to_le32(lower_32_bits(dma_addr)); + rxd->addrhigh = cpu_to_le32(upper_32_bits(dma_addr)); + rxd->ctrl1 = cpu_to_le32(flags); + rxd->ctrl2 = cpu_to_le32(buf_len); + + /* bump ring index and return */ + pdcs->rxout = NEXTRXD(pdcs->rxout, pdcs->nrxpost); +} + +/** + * pdc_build_txd() - Build a DMA descriptor to transmit a SPU request to + * hardware. + * @pdcs: PDC state for the SPU that will process this request + * @dma_addr: DMA address of packet to be transmitted + * @buf_len: Length of tx buffer, in bytes + * @flags: Flags to be stored in descriptor + */ +static inline void +pdc_build_txd(struct pdc_state *pdcs, dma_addr_t dma_addr, u32 buf_len, + u32 flags) +{ + struct device *dev = &pdcs->pdev->dev; + struct dma64dd *txd = &pdcs->txd_64[pdcs->txout]; + + dev_dbg(dev, + "Writing tx descriptor for PDC %u at index %u with length %u, flags %#x\n", + pdcs->pdc_idx, pdcs->txout, buf_len, flags); + + txd->addrlow = cpu_to_le32(lower_32_bits(dma_addr)); + txd->addrhigh = cpu_to_le32(upper_32_bits(dma_addr)); + txd->ctrl1 = cpu_to_le32(flags); + txd->ctrl2 = cpu_to_le32(buf_len); + + /* bump ring index and return */ + pdcs->txout = NEXTTXD(pdcs->txout, pdcs->ntxpost); +} + +/** + * pdc_receive_one() - Receive a response message from a given SPU. + * @pdcs: PDC state for the SPU to receive from + * + * When the return code indicates success, the response message is available in + * the receive buffers provided prior to submission of the request. + * + * Return: PDC_SUCCESS if one or more receive descriptors was processed + * -EAGAIN indicates that no response message is available + * -EIO an error occurred + */ +static int +pdc_receive_one(struct pdc_state *pdcs) +{ + struct device *dev = &pdcs->pdev->dev; + struct mbox_controller *mbc; + struct mbox_chan *chan; + struct brcm_message mssg; + u32 len, rx_status; + u32 num_frags; + u8 *resp_hdr; /* virtual addr of start of resp message DMA header */ + u32 frags_rdy; /* number of fragments ready to read */ + u32 rx_idx; /* ring index of start of receive frame */ + dma_addr_t resp_hdr_daddr; + struct pdc_rx_ctx *rx_ctx; + + mbc = &pdcs->mbc; + chan = &mbc->chans[0]; + mssg.type = BRCM_MESSAGE_SPU; + + /* + * return if a complete response message is not yet ready. + * rxin_numd[rxin] is the number of fragments in the next msg + * to read. + */ + frags_rdy = NRXDACTIVE(pdcs->rxin, pdcs->last_rx_curr, pdcs->nrxpost); + if ((frags_rdy == 0) || + (frags_rdy < pdcs->rx_ctx[pdcs->rxin].rxin_numd)) + /* No response ready */ + return -EAGAIN; + + num_frags = pdcs->txin_numd[pdcs->txin]; + WARN_ON(num_frags == 0); + + dma_unmap_sg(dev, pdcs->src_sg[pdcs->txin], + sg_nents(pdcs->src_sg[pdcs->txin]), DMA_TO_DEVICE); + + pdcs->txin = (pdcs->txin + num_frags) & pdcs->ntxpost; + + dev_dbg(dev, "PDC %u reclaimed %d tx descriptors", + pdcs->pdc_idx, num_frags); + + rx_idx = pdcs->rxin; + rx_ctx = &pdcs->rx_ctx[rx_idx]; + num_frags = rx_ctx->rxin_numd; + /* Return opaque context with result */ + mssg.ctx = rx_ctx->rxp_ctx; + rx_ctx->rxp_ctx = NULL; + resp_hdr = rx_ctx->resp_hdr; + resp_hdr_daddr = rx_ctx->resp_hdr_daddr; + dma_unmap_sg(dev, rx_ctx->dst_sg, sg_nents(rx_ctx->dst_sg), + DMA_FROM_DEVICE); + + pdcs->rxin = (pdcs->rxin + num_frags) & pdcs->nrxpost; + + dev_dbg(dev, "PDC %u reclaimed %d rx descriptors", + pdcs->pdc_idx, num_frags); + + dev_dbg(dev, + "PDC %u txin %u, txout %u, rxin %u, rxout %u, last_rx_curr %u\n", + pdcs->pdc_idx, pdcs->txin, pdcs->txout, pdcs->rxin, + pdcs->rxout, pdcs->last_rx_curr); + + if (pdcs->pdc_resp_hdr_len == PDC_SPUM_RESP_HDR_LEN) { + /* + * For SPU-M, get length of response msg and rx overflow status. + */ + rx_status = *((u32 *)resp_hdr); + len = rx_status & RX_STATUS_LEN; + dev_dbg(dev, + "SPU response length %u bytes", len); + if (unlikely(((rx_status & RX_STATUS_OVERFLOW) || (!len)))) { + if (rx_status & RX_STATUS_OVERFLOW) { + dev_err_ratelimited(dev, + "crypto receive overflow"); + pdcs->rx_oflow++; + } else { + dev_info_ratelimited(dev, "crypto rx len = 0"); + } + return -EIO; + } + } + + dma_pool_free(pdcs->rx_buf_pool, resp_hdr, resp_hdr_daddr); + + mbox_chan_received_data(chan, &mssg); + + pdcs->pdc_replies++; + return PDC_SUCCESS; +} + +/** + * pdc_receive() - Process as many responses as are available in the rx ring. + * @pdcs: PDC state + * + * Called within the hard IRQ. + * Return: + */ +static int +pdc_receive(struct pdc_state *pdcs) +{ + int rx_status; + + /* read last_rx_curr from register once */ + pdcs->last_rx_curr = + (ioread32((const void __iomem *)&pdcs->rxregs_64->status0) & + CRYPTO_D64_RS0_CD_MASK) / RING_ENTRY_SIZE; + + do { + /* Could be many frames ready */ + rx_status = pdc_receive_one(pdcs); + } while (rx_status == PDC_SUCCESS); + + return 0; +} + +/** + * pdc_tx_list_sg_add() - Add the buffers in a scatterlist to the transmit + * descriptors for a given SPU. The scatterlist buffers contain the data for a + * SPU request message. + * @pdcs: PDC state for the SPU that will process this request + * @sg: Scatterlist whose buffers contain part of the SPU request + * + * If a scatterlist buffer is larger than PDC_DMA_BUF_MAX, multiple descriptors + * are written for that buffer, each <= PDC_DMA_BUF_MAX byte in length. + * + * Return: PDC_SUCCESS if successful + * < 0 otherwise + */ +static int pdc_tx_list_sg_add(struct pdc_state *pdcs, struct scatterlist *sg) +{ + u32 flags = 0; + u32 eot; + u32 tx_avail; + + /* + * Num descriptors needed. Conservatively assume we need a descriptor + * for every entry in sg. + */ + u32 num_desc; + u32 desc_w = 0; /* Number of tx descriptors written */ + u32 bufcnt; /* Number of bytes of buffer pointed to by descriptor */ + dma_addr_t databufptr; /* DMA address to put in descriptor */ + + num_desc = (u32)sg_nents(sg); + + /* check whether enough tx descriptors are available */ + tx_avail = pdcs->ntxpost - NTXDACTIVE(pdcs->txin, pdcs->txout, + pdcs->ntxpost); + if (unlikely(num_desc > tx_avail)) { + pdcs->txnobuf++; + return -ENOSPC; + } + + /* build tx descriptors */ + if (pdcs->tx_msg_start == pdcs->txout) { + /* Start of frame */ + pdcs->txin_numd[pdcs->tx_msg_start] = 0; + pdcs->src_sg[pdcs->txout] = sg; + flags = D64_CTRL1_SOF; + } + + while (sg) { + if (unlikely(pdcs->txout == (pdcs->ntxd - 1))) + eot = D64_CTRL1_EOT; + else + eot = 0; + + /* + * If sg buffer larger than PDC limit, split across + * multiple descriptors + */ + bufcnt = sg_dma_len(sg); + databufptr = sg_dma_address(sg); + while (bufcnt > PDC_DMA_BUF_MAX) { + pdc_build_txd(pdcs, databufptr, PDC_DMA_BUF_MAX, + flags | eot); + desc_w++; + bufcnt -= PDC_DMA_BUF_MAX; + databufptr += PDC_DMA_BUF_MAX; + if (unlikely(pdcs->txout == (pdcs->ntxd - 1))) + eot = D64_CTRL1_EOT; + else + eot = 0; + } + sg = sg_next(sg); + if (!sg) + /* Writing last descriptor for frame */ + flags |= (D64_CTRL1_EOF | D64_CTRL1_IOC); + pdc_build_txd(pdcs, databufptr, bufcnt, flags | eot); + desc_w++; + /* Clear start of frame after first descriptor */ + flags &= ~D64_CTRL1_SOF; + } + pdcs->txin_numd[pdcs->tx_msg_start] += desc_w; + + return PDC_SUCCESS; +} + +/** + * pdc_tx_list_final() - Initiate DMA transfer of last frame written to tx + * ring. + * @pdcs: PDC state for SPU to process the request + * + * Sets the index of the last descriptor written in both the rx and tx ring. + * + * Return: PDC_SUCCESS + */ +static int pdc_tx_list_final(struct pdc_state *pdcs) +{ + /* + * write barrier to ensure all register writes are complete + * before chip starts to process new request + */ + wmb(); + iowrite32(pdcs->rxout << 4, &pdcs->rxregs_64->ptr); + iowrite32(pdcs->txout << 4, &pdcs->txregs_64->ptr); + pdcs->pdc_requests++; + + return PDC_SUCCESS; +} + +/** + * pdc_rx_list_init() - Start a new receive descriptor list for a given PDC. + * @pdcs: PDC state for SPU handling request + * @dst_sg: scatterlist providing rx buffers for response to be returned to + * mailbox client + * @ctx: Opaque context for this request + * + * Posts a single receive descriptor to hold the metadata that precedes a + * response. For example, with SPU-M, the metadata is a 32-byte DMA header and + * an 8-byte BCM header. Moves the msg_start descriptor indexes for both tx and + * rx to indicate the start of a new message. + * + * Return: PDC_SUCCESS if successful + * < 0 if an error (e.g., rx ring is full) + */ +static int pdc_rx_list_init(struct pdc_state *pdcs, struct scatterlist *dst_sg, + void *ctx) +{ + u32 flags = 0; + u32 rx_avail; + u32 rx_pkt_cnt = 1; /* Adding a single rx buffer */ + dma_addr_t daddr; + void *vaddr; + struct pdc_rx_ctx *rx_ctx; + + rx_avail = pdcs->nrxpost - NRXDACTIVE(pdcs->rxin, pdcs->rxout, + pdcs->nrxpost); + if (unlikely(rx_pkt_cnt > rx_avail)) { + pdcs->rxnobuf++; + return -ENOSPC; + } + + /* allocate a buffer for the dma rx status */ + vaddr = dma_pool_zalloc(pdcs->rx_buf_pool, GFP_ATOMIC, &daddr); + if (unlikely(!vaddr)) + return -ENOMEM; + + /* + * Update msg_start indexes for both tx and rx to indicate the start + * of a new sequence of descriptor indexes that contain the fragments + * of the same message. + */ + pdcs->rx_msg_start = pdcs->rxout; + pdcs->tx_msg_start = pdcs->txout; + + /* This is always the first descriptor in the receive sequence */ + flags = D64_CTRL1_SOF; + pdcs->rx_ctx[pdcs->rx_msg_start].rxin_numd = 1; + + if (unlikely(pdcs->rxout == (pdcs->nrxd - 1))) + flags |= D64_CTRL1_EOT; + + rx_ctx = &pdcs->rx_ctx[pdcs->rxout]; + rx_ctx->rxp_ctx = ctx; + rx_ctx->dst_sg = dst_sg; + rx_ctx->resp_hdr = vaddr; + rx_ctx->resp_hdr_daddr = daddr; + pdc_build_rxd(pdcs, daddr, pdcs->pdc_resp_hdr_len, flags); + return PDC_SUCCESS; +} + +/** + * pdc_rx_list_sg_add() - Add the buffers in a scatterlist to the receive + * descriptors for a given SPU. The caller must have already DMA mapped the + * scatterlist. + * @pdcs: PDC state for the SPU that will process this request + * @sg: Scatterlist whose buffers are added to the receive ring + * + * If a receive buffer in the scatterlist is larger than PDC_DMA_BUF_MAX, + * multiple receive descriptors are written, each with a buffer <= + * PDC_DMA_BUF_MAX. + * + * Return: PDC_SUCCESS if successful + * < 0 otherwise (e.g., receive ring is full) + */ +static int pdc_rx_list_sg_add(struct pdc_state *pdcs, struct scatterlist *sg) +{ + u32 flags = 0; + u32 rx_avail; + + /* + * Num descriptors needed. Conservatively assume we need a descriptor + * for every entry from our starting point in the scatterlist. + */ + u32 num_desc; + u32 desc_w = 0; /* Number of tx descriptors written */ + u32 bufcnt; /* Number of bytes of buffer pointed to by descriptor */ + dma_addr_t databufptr; /* DMA address to put in descriptor */ + + num_desc = (u32)sg_nents(sg); + + rx_avail = pdcs->nrxpost - NRXDACTIVE(pdcs->rxin, pdcs->rxout, + pdcs->nrxpost); + if (unlikely(num_desc > rx_avail)) { + pdcs->rxnobuf++; + return -ENOSPC; + } + + while (sg) { + if (unlikely(pdcs->rxout == (pdcs->nrxd - 1))) + flags = D64_CTRL1_EOT; + else + flags = 0; + + /* + * If sg buffer larger than PDC limit, split across + * multiple descriptors + */ + bufcnt = sg_dma_len(sg); + databufptr = sg_dma_address(sg); + while (bufcnt > PDC_DMA_BUF_MAX) { + pdc_build_rxd(pdcs, databufptr, PDC_DMA_BUF_MAX, flags); + desc_w++; + bufcnt -= PDC_DMA_BUF_MAX; + databufptr += PDC_DMA_BUF_MAX; + if (unlikely(pdcs->rxout == (pdcs->nrxd - 1))) + flags = D64_CTRL1_EOT; + else + flags = 0; + } + pdc_build_rxd(pdcs, databufptr, bufcnt, flags); + desc_w++; + sg = sg_next(sg); + } + pdcs->rx_ctx[pdcs->rx_msg_start].rxin_numd += desc_w; + + return PDC_SUCCESS; +} + +/** + * pdc_irq_handler() - Interrupt handler called in interrupt context. + * @irq: Interrupt number that has fired + * @data: device struct for DMA engine that generated the interrupt + * + * We have to clear the device interrupt status flags here. So cache the + * status for later use in the thread function. Other than that, just return + * WAKE_THREAD to invoke the thread function. + * + * Return: IRQ_WAKE_THREAD if interrupt is ours + * IRQ_NONE otherwise + */ +static irqreturn_t pdc_irq_handler(int irq, void *data) +{ + struct device *dev = (struct device *)data; + struct pdc_state *pdcs = dev_get_drvdata(dev); + u32 intstatus = ioread32(pdcs->pdc_reg_vbase + PDC_INTSTATUS_OFFSET); + + if (unlikely(intstatus == 0)) + return IRQ_NONE; + + /* Disable interrupts until soft handler runs */ + iowrite32(0, pdcs->pdc_reg_vbase + PDC_INTMASK_OFFSET); + + /* Clear interrupt flags in device */ + iowrite32(intstatus, pdcs->pdc_reg_vbase + PDC_INTSTATUS_OFFSET); + + /* Wakeup IRQ thread */ + queue_work(system_bh_wq, &pdcs->rx_work); + return IRQ_HANDLED; +} + +/** + * pdc_work_cb() - Work callback that runs the deferred processing after + * a DMA receive interrupt. Reenables the receive interrupt. + * @t: Pointer to the Altera sSGDMA channel structure + */ +static void pdc_work_cb(struct work_struct *t) +{ + struct pdc_state *pdcs = from_work(pdcs, t, rx_work); + + pdc_receive(pdcs); + + /* reenable interrupts */ + iowrite32(PDC_INTMASK, pdcs->pdc_reg_vbase + PDC_INTMASK_OFFSET); +} + +/** + * pdc_ring_init() - Allocate DMA rings and initialize constant fields of + * descriptors in one ringset. + * @pdcs: PDC instance state + * @ringset: index of ringset being used + * + * Return: PDC_SUCCESS if ring initialized + * < 0 otherwise + */ +static int pdc_ring_init(struct pdc_state *pdcs, int ringset) +{ + int i; + int err = PDC_SUCCESS; + struct dma64 *dma_reg; + struct device *dev = &pdcs->pdev->dev; + struct pdc_ring_alloc tx; + struct pdc_ring_alloc rx; + + /* Allocate tx ring */ + tx.vbase = dma_pool_zalloc(pdcs->ring_pool, GFP_KERNEL, &tx.dmabase); + if (unlikely(!tx.vbase)) { + err = -ENOMEM; + goto done; + } + + /* Allocate rx ring */ + rx.vbase = dma_pool_zalloc(pdcs->ring_pool, GFP_KERNEL, &rx.dmabase); + if (unlikely(!rx.vbase)) { + err = -ENOMEM; + goto fail_dealloc; + } + + dev_dbg(dev, " - base DMA addr of tx ring %pad", &tx.dmabase); + dev_dbg(dev, " - base virtual addr of tx ring %p", tx.vbase); + dev_dbg(dev, " - base DMA addr of rx ring %pad", &rx.dmabase); + dev_dbg(dev, " - base virtual addr of rx ring %p", rx.vbase); + + memcpy(&pdcs->tx_ring_alloc, &tx, sizeof(tx)); + memcpy(&pdcs->rx_ring_alloc, &rx, sizeof(rx)); + + pdcs->rxin = 0; + pdcs->rx_msg_start = 0; + pdcs->last_rx_curr = 0; + pdcs->rxout = 0; + pdcs->txin = 0; + pdcs->tx_msg_start = 0; + pdcs->txout = 0; + + /* Set descriptor array base addresses */ + pdcs->txd_64 = (struct dma64dd *)pdcs->tx_ring_alloc.vbase; + pdcs->rxd_64 = (struct dma64dd *)pdcs->rx_ring_alloc.vbase; + + /* Tell device the base DMA address of each ring */ + dma_reg = &pdcs->regs->dmaregs[ringset]; + + /* But first disable DMA and set curptr to 0 for both TX & RX */ + iowrite32(PDC_TX_CTL, &dma_reg->dmaxmt.control); + iowrite32((PDC_RX_CTL + (pdcs->rx_status_len << 1)), + &dma_reg->dmarcv.control); + iowrite32(0, &dma_reg->dmaxmt.ptr); + iowrite32(0, &dma_reg->dmarcv.ptr); + + /* Set base DMA addresses */ + iowrite32(lower_32_bits(pdcs->tx_ring_alloc.dmabase), + &dma_reg->dmaxmt.addrlow); + iowrite32(upper_32_bits(pdcs->tx_ring_alloc.dmabase), + &dma_reg->dmaxmt.addrhigh); + + iowrite32(lower_32_bits(pdcs->rx_ring_alloc.dmabase), + &dma_reg->dmarcv.addrlow); + iowrite32(upper_32_bits(pdcs->rx_ring_alloc.dmabase), + &dma_reg->dmarcv.addrhigh); + + /* Re-enable DMA */ + iowrite32(PDC_TX_CTL | PDC_TX_ENABLE, &dma_reg->dmaxmt.control); + iowrite32((PDC_RX_CTL | PDC_RX_ENABLE | (pdcs->rx_status_len << 1)), + &dma_reg->dmarcv.control); + + /* Initialize descriptors */ + for (i = 0; i < PDC_RING_ENTRIES; i++) { + /* Every tx descriptor can be used for start of frame. */ + if (i != pdcs->ntxpost) { + iowrite32(D64_CTRL1_SOF | D64_CTRL1_EOF, + &pdcs->txd_64[i].ctrl1); + } else { + /* Last descriptor in ringset. Set End of Table. */ + iowrite32(D64_CTRL1_SOF | D64_CTRL1_EOF | + D64_CTRL1_EOT, &pdcs->txd_64[i].ctrl1); + } + + /* Every rx descriptor can be used for start of frame */ + if (i != pdcs->nrxpost) { + iowrite32(D64_CTRL1_SOF, + &pdcs->rxd_64[i].ctrl1); + } else { + /* Last descriptor in ringset. Set End of Table. */ + iowrite32(D64_CTRL1_SOF | D64_CTRL1_EOT, + &pdcs->rxd_64[i].ctrl1); + } + } + return PDC_SUCCESS; + +fail_dealloc: + dma_pool_free(pdcs->ring_pool, tx.vbase, tx.dmabase); +done: + return err; +} + +static void pdc_ring_free(struct pdc_state *pdcs) +{ + if (pdcs->tx_ring_alloc.vbase) { + dma_pool_free(pdcs->ring_pool, pdcs->tx_ring_alloc.vbase, + pdcs->tx_ring_alloc.dmabase); + pdcs->tx_ring_alloc.vbase = NULL; + } + + if (pdcs->rx_ring_alloc.vbase) { + dma_pool_free(pdcs->ring_pool, pdcs->rx_ring_alloc.vbase, + pdcs->rx_ring_alloc.dmabase); + pdcs->rx_ring_alloc.vbase = NULL; + } +} + +/** + * pdc_desc_count() - Count the number of DMA descriptors that will be required + * for a given scatterlist. Account for the max length of a DMA buffer. + * @sg: Scatterlist to be DMA'd + * Return: Number of descriptors required + */ +static u32 pdc_desc_count(struct scatterlist *sg) +{ + u32 cnt = 0; + + while (sg) { + cnt += ((sg->length / PDC_DMA_BUF_MAX) + 1); + sg = sg_next(sg); + } + return cnt; +} + +/** + * pdc_rings_full() - Check whether the tx ring has room for tx_cnt descriptors + * and the rx ring has room for rx_cnt descriptors. + * @pdcs: PDC state + * @tx_cnt: The number of descriptors required in the tx ring + * @rx_cnt: The number of descriptors required i the rx ring + * + * Return: true if one of the rings does not have enough space + * false if sufficient space is available in both rings + */ +static bool pdc_rings_full(struct pdc_state *pdcs, int tx_cnt, int rx_cnt) +{ + u32 rx_avail; + u32 tx_avail; + bool full = false; + + /* Check if the tx and rx rings are likely to have enough space */ + rx_avail = pdcs->nrxpost - NRXDACTIVE(pdcs->rxin, pdcs->rxout, + pdcs->nrxpost); + if (unlikely(rx_cnt > rx_avail)) { + pdcs->rx_ring_full++; + full = true; + } + + if (likely(!full)) { + tx_avail = pdcs->ntxpost - NTXDACTIVE(pdcs->txin, pdcs->txout, + pdcs->ntxpost); + if (unlikely(tx_cnt > tx_avail)) { + pdcs->tx_ring_full++; + full = true; + } + } + return full; +} + +/** + * pdc_last_tx_done() - If both the tx and rx rings have at least + * PDC_RING_SPACE_MIN descriptors available, then indicate that the mailbox + * framework can submit another message. + * @chan: mailbox channel to check + * Return: true if PDC can accept another message on this channel + */ +static bool pdc_last_tx_done(struct mbox_chan *chan) +{ + struct pdc_state *pdcs = chan->con_priv; + bool ret; + + if (unlikely(pdc_rings_full(pdcs, PDC_RING_SPACE_MIN, + PDC_RING_SPACE_MIN))) { + pdcs->last_tx_not_done++; + ret = false; + } else { + ret = true; + } + return ret; +} + +/** + * pdc_send_data() - mailbox send_data function + * @chan: The mailbox channel on which the data is sent. The channel + * corresponds to a DMA ringset. + * @data: The mailbox message to be sent. The message must be a + * brcm_message structure. + * + * This function is registered as the send_data function for the mailbox + * controller. From the destination scatterlist in the mailbox message, it + * creates a sequence of receive descriptors in the rx ring. From the source + * scatterlist, it creates a sequence of transmit descriptors in the tx ring. + * After creating the descriptors, it writes the rx ptr and tx ptr registers to + * initiate the DMA transfer. + * + * This function does the DMA map and unmap of the src and dst scatterlists in + * the mailbox message. + * + * Return: 0 if successful + * -ENOTSUPP if the mailbox message is a type this driver does not + * support + * < 0 if an error + */ +static int pdc_send_data(struct mbox_chan *chan, void *data) +{ + struct pdc_state *pdcs = chan->con_priv; + struct device *dev = &pdcs->pdev->dev; + struct brcm_message *mssg = data; + int err = PDC_SUCCESS; + int src_nent; + int dst_nent; + int nent; + u32 tx_desc_req; + u32 rx_desc_req; + + if (unlikely(mssg->type != BRCM_MESSAGE_SPU)) + return -ENOTSUPP; + + src_nent = sg_nents(mssg->spu.src); + if (likely(src_nent)) { + nent = dma_map_sg(dev, mssg->spu.src, src_nent, DMA_TO_DEVICE); + if (unlikely(nent == 0)) + return -EIO; + } + + dst_nent = sg_nents(mssg->spu.dst); + if (likely(dst_nent)) { + nent = dma_map_sg(dev, mssg->spu.dst, dst_nent, + DMA_FROM_DEVICE); + if (unlikely(nent == 0)) { + dma_unmap_sg(dev, mssg->spu.src, src_nent, + DMA_TO_DEVICE); + return -EIO; + } + } + + /* + * Check if the tx and rx rings have enough space. Do this prior to + * writing any tx or rx descriptors. Need to ensure that we do not write + * a partial set of descriptors, or write just rx descriptors but + * corresponding tx descriptors don't fit. Note that we want this check + * and the entire sequence of descriptor to happen without another + * thread getting in. The channel spin lock in the mailbox framework + * ensures this. + */ + tx_desc_req = pdc_desc_count(mssg->spu.src); + rx_desc_req = pdc_desc_count(mssg->spu.dst); + if (unlikely(pdc_rings_full(pdcs, tx_desc_req, rx_desc_req + 1))) + return -ENOSPC; + + /* Create rx descriptors to SPU catch response */ + err = pdc_rx_list_init(pdcs, mssg->spu.dst, mssg->ctx); + err |= pdc_rx_list_sg_add(pdcs, mssg->spu.dst); + + /* Create tx descriptors to submit SPU request */ + err |= pdc_tx_list_sg_add(pdcs, mssg->spu.src); + err |= pdc_tx_list_final(pdcs); /* initiate transfer */ + + if (unlikely(err)) + dev_err(&pdcs->pdev->dev, + "%s failed with error %d", __func__, err); + + return err; +} + +static int pdc_startup(struct mbox_chan *chan) +{ + return pdc_ring_init(chan->con_priv, PDC_RINGSET); +} + +static void pdc_shutdown(struct mbox_chan *chan) +{ + struct pdc_state *pdcs = chan->con_priv; + + if (!pdcs) + return; + + dev_dbg(&pdcs->pdev->dev, + "Shutdown mailbox channel for PDC %u", pdcs->pdc_idx); + pdc_ring_free(pdcs); +} + +/** + * pdc_hw_init() - Use the given initialization parameters to initialize the + * state for one of the PDCs. + * @pdcs: state of the PDC + */ +static +void pdc_hw_init(struct pdc_state *pdcs) +{ + struct platform_device *pdev; + struct device *dev; + struct dma64 *dma_reg; + int ringset = PDC_RINGSET; + + pdev = pdcs->pdev; + dev = &pdev->dev; + + dev_dbg(dev, "PDC %u initial values:", pdcs->pdc_idx); + dev_dbg(dev, "state structure: %p", + pdcs); + dev_dbg(dev, " - base virtual addr of hw regs %p", + pdcs->pdc_reg_vbase); + + /* initialize data structures */ + pdcs->regs = (struct pdc_regs *)pdcs->pdc_reg_vbase; + pdcs->txregs_64 = (struct dma64_regs *) + (((u8 *)pdcs->pdc_reg_vbase) + + PDC_TXREGS_OFFSET + (sizeof(struct dma64) * ringset)); + pdcs->rxregs_64 = (struct dma64_regs *) + (((u8 *)pdcs->pdc_reg_vbase) + + PDC_RXREGS_OFFSET + (sizeof(struct dma64) * ringset)); + + pdcs->ntxd = PDC_RING_ENTRIES; + pdcs->nrxd = PDC_RING_ENTRIES; + pdcs->ntxpost = PDC_RING_ENTRIES - 1; + pdcs->nrxpost = PDC_RING_ENTRIES - 1; + iowrite32(0, &pdcs->regs->intmask); + + dma_reg = &pdcs->regs->dmaregs[ringset]; + + /* Configure DMA but will enable later in pdc_ring_init() */ + iowrite32(PDC_TX_CTL, &dma_reg->dmaxmt.control); + + iowrite32(PDC_RX_CTL + (pdcs->rx_status_len << 1), + &dma_reg->dmarcv.control); + + /* Reset current index pointers after making sure DMA is disabled */ + iowrite32(0, &dma_reg->dmaxmt.ptr); + iowrite32(0, &dma_reg->dmarcv.ptr); + + if (pdcs->pdc_resp_hdr_len == PDC_SPU2_RESP_HDR_LEN) + iowrite32(PDC_CKSUM_CTRL, + pdcs->pdc_reg_vbase + PDC_CKSUM_CTRL_OFFSET); +} + +/** + * pdc_hw_disable() - Disable the tx and rx control in the hw. + * @pdcs: PDC state structure + * + */ +static void pdc_hw_disable(struct pdc_state *pdcs) +{ + struct dma64 *dma_reg; + + dma_reg = &pdcs->regs->dmaregs[PDC_RINGSET]; + iowrite32(PDC_TX_CTL, &dma_reg->dmaxmt.control); + iowrite32(PDC_RX_CTL + (pdcs->rx_status_len << 1), + &dma_reg->dmarcv.control); +} + +/** + * pdc_rx_buf_pool_create() - Pool of receive buffers used to catch the metadata + * header returned with each response message. + * @pdcs: PDC state structure + * + * The metadata is not returned to the mailbox client. So the PDC driver + * manages these buffers. + * + * Return: PDC_SUCCESS + * -ENOMEM if pool creation fails + */ +static int pdc_rx_buf_pool_create(struct pdc_state *pdcs) +{ + struct platform_device *pdev; + struct device *dev; + + pdev = pdcs->pdev; + dev = &pdev->dev; + + pdcs->pdc_resp_hdr_len = pdcs->rx_status_len; + if (pdcs->use_bcm_hdr) + pdcs->pdc_resp_hdr_len += BCM_HDR_LEN; + + pdcs->rx_buf_pool = dma_pool_create("pdc rx bufs", dev, + pdcs->pdc_resp_hdr_len, + RX_BUF_ALIGN, 0); + if (!pdcs->rx_buf_pool) + return -ENOMEM; + + return PDC_SUCCESS; +} + +/** + * pdc_interrupts_init() - Initialize the interrupt configuration for a PDC and + * specify a threaded IRQ handler for deferred handling of interrupts outside of + * interrupt context. + * @pdcs: PDC state + * + * Set the interrupt mask for transmit and receive done. + * Set the lazy interrupt frame count to generate an interrupt for just one pkt. + * + * Return: PDC_SUCCESS + * <0 if threaded irq request fails + */ +static int pdc_interrupts_init(struct pdc_state *pdcs) +{ + struct platform_device *pdev = pdcs->pdev; + struct device *dev = &pdev->dev; + struct device_node *dn = pdev->dev.of_node; + int err; + + /* interrupt configuration */ + iowrite32(PDC_INTMASK, pdcs->pdc_reg_vbase + PDC_INTMASK_OFFSET); + + if (pdcs->hw_type == FA_HW) + iowrite32(PDC_LAZY_INT, pdcs->pdc_reg_vbase + + FA_RCVLAZY0_OFFSET); + else + iowrite32(PDC_LAZY_INT, pdcs->pdc_reg_vbase + + PDC_RCVLAZY0_OFFSET); + + /* read irq from device tree */ + pdcs->pdc_irq = irq_of_parse_and_map(dn, 0); + dev_dbg(dev, "pdc device %s irq %u for pdcs %p", + dev_name(dev), pdcs->pdc_irq, pdcs); + + err = devm_request_irq(dev, pdcs->pdc_irq, pdc_irq_handler, 0, + dev_name(dev), dev); + if (err) + return err; + return PDC_SUCCESS; +} + +static const struct mbox_chan_ops pdc_mbox_chan_ops = { + .send_data = pdc_send_data, + .last_tx_done = pdc_last_tx_done, + .startup = pdc_startup, + .shutdown = pdc_shutdown +}; + +/** + * pdc_mb_init() - Initialize the mailbox controller. + * @pdcs: PDC state + * + * Each PDC is a mailbox controller. Each ringset is a mailbox channel. Kernel + * driver only uses one ringset and thus one mb channel. PDC uses the transmit + * complete interrupt to determine when a mailbox message has successfully been + * transmitted. + * + * Return: 0 on success + * < 0 if there is an allocation or registration failure + */ +static int pdc_mb_init(struct pdc_state *pdcs) +{ + struct device *dev = &pdcs->pdev->dev; + struct mbox_controller *mbc; + int chan_index; + int err; + + mbc = &pdcs->mbc; + mbc->dev = dev; + mbc->ops = &pdc_mbox_chan_ops; + mbc->num_chans = 1; + mbc->chans = devm_kcalloc(dev, mbc->num_chans, sizeof(*mbc->chans), + GFP_KERNEL); + if (!mbc->chans) + return -ENOMEM; + + mbc->txdone_irq = false; + mbc->txdone_poll = true; + mbc->txpoll_period = 1; + for (chan_index = 0; chan_index < mbc->num_chans; chan_index++) + mbc->chans[chan_index].con_priv = pdcs; + + /* Register mailbox controller */ + err = devm_mbox_controller_register(dev, mbc); + if (err) { + dev_crit(dev, + "Failed to register PDC mailbox controller. Error %d.", + err); + return err; + } + return 0; +} + +/* Device tree API */ +static const int pdc_hw = PDC_HW; +static const int fa_hw = FA_HW; + +static const struct of_device_id pdc_mbox_of_match[] = { + {.compatible = "brcm,iproc-pdc-mbox", .data = &pdc_hw}, + {.compatible = "brcm,iproc-fa2-mbox", .data = &fa_hw}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pdc_mbox_of_match); + +/** + * pdc_dt_read() - Read application-specific data from device tree. + * @pdev: Platform device + * @pdcs: PDC state + * + * Reads the number of bytes of receive status that precede each received frame. + * Reads whether transmit and received frames should be preceded by an 8-byte + * BCM header. + * + * Return: 0 if successful + * -ENODEV if device not available + */ +static int pdc_dt_read(struct platform_device *pdev, struct pdc_state *pdcs) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = pdev->dev.of_node; + const int *hw_type; + int err; + + err = of_property_read_u32(dn, "brcm,rx-status-len", + &pdcs->rx_status_len); + if (err < 0) + dev_err(dev, + "%s failed to get DMA receive status length from device tree", + __func__); + + pdcs->use_bcm_hdr = of_property_read_bool(dn, "brcm,use-bcm-hdr"); + + pdcs->hw_type = PDC_HW; + + hw_type = device_get_match_data(dev); + if (hw_type) + pdcs->hw_type = *hw_type; + + return 0; +} + +/** + * pdc_probe() - Probe function for PDC driver. + * @pdev: PDC platform device + * + * Reserve and map register regions defined in device tree. + * Allocate and initialize tx and rx DMA rings. + * Initialize a mailbox controller for each PDC. + * + * Return: 0 if successful + * < 0 if an error + */ +static int pdc_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; + struct resource *pdc_regs; + struct pdc_state *pdcs; + + /* PDC state for one SPU */ + pdcs = devm_kzalloc(dev, sizeof(*pdcs), GFP_KERNEL); + if (!pdcs) { + err = -ENOMEM; + goto cleanup; + } + + pdcs->pdev = pdev; + platform_set_drvdata(pdev, pdcs); + pdcs->pdc_idx = pdcg.num_spu; + pdcg.num_spu++; + + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39)); + if (err) { + dev_warn(dev, "PDC device cannot perform DMA. Error %d.", err); + goto cleanup; + } + + /* Create DMA pool for tx ring */ + pdcs->ring_pool = dma_pool_create("pdc rings", dev, PDC_RING_SIZE, + RING_ALIGN, 0); + if (!pdcs->ring_pool) { + err = -ENOMEM; + goto cleanup; + } + + err = pdc_dt_read(pdev, pdcs); + if (err) + goto cleanup_ring_pool; + + pdcs->pdc_reg_vbase = devm_platform_get_and_ioremap_resource(pdev, 0, &pdc_regs); + if (IS_ERR(pdcs->pdc_reg_vbase)) { + err = PTR_ERR(pdcs->pdc_reg_vbase); + goto cleanup_ring_pool; + } + dev_dbg(dev, "PDC register region res.start = %pa, res.end = %pa", + &pdc_regs->start, &pdc_regs->end); + + /* create rx buffer pool after dt read to know how big buffers are */ + err = pdc_rx_buf_pool_create(pdcs); + if (err) + goto cleanup_ring_pool; + + pdc_hw_init(pdcs); + + /* Init work for deferred DMA rx processing */ + INIT_WORK(&pdcs->rx_work, pdc_work_cb); + + err = pdc_interrupts_init(pdcs); + if (err) + goto cleanup_buf_pool; + + /* Initialize mailbox controller */ + err = pdc_mb_init(pdcs); + if (err) + goto cleanup_buf_pool; + + pdc_setup_debugfs(pdcs); + + dev_dbg(dev, "pdc_probe() successful"); + return PDC_SUCCESS; + +cleanup_buf_pool: + cancel_work_sync(&pdcs->rx_work); + dma_pool_destroy(pdcs->rx_buf_pool); + +cleanup_ring_pool: + dma_pool_destroy(pdcs->ring_pool); + +cleanup: + return err; +} + +static void pdc_remove(struct platform_device *pdev) +{ + struct pdc_state *pdcs = platform_get_drvdata(pdev); + + pdc_free_debugfs(); + + cancel_work_sync(&pdcs->rx_work); + + pdc_hw_disable(pdcs); + + dma_pool_destroy(pdcs->rx_buf_pool); + dma_pool_destroy(pdcs->ring_pool); +} + +static struct platform_driver pdc_mbox_driver = { + .probe = pdc_probe, + .remove = pdc_remove, + .driver = { + .name = "brcm-iproc-pdc-mbox", + .of_match_table = pdc_mbox_of_match, + }, +}; +module_platform_driver(pdc_mbox_driver); + +MODULE_AUTHOR("Rob Rice <rob.rice@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom PDC mailbox driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c new file mode 100644 index 000000000..0ca75c378 --- /dev/null +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2010,2015 Broadcom + * Copyright (C) 2013-2014 Lubomir Rintel + * Copyright (C) 2013 Craig McGeachie + * + * Parts of the driver are based on: + * - arch/arm/mach-bcm2708/vcio.c file written by Gray Girling that was + * obtained from branch "rpi-3.6.y" of git://github.com/raspberrypi/ + * linux.git + * - drivers/mailbox/bcm2835-ipc.c by Lubomir Rintel at + * https://github.com/hackerspace/rpi-linux/blob/lr-raspberry-pi/drivers/ + * mailbox/bcm2835-ipc.c + * - documentation available on the following web site: + * https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +/* Mailboxes */ +#define ARM_0_MAIL0 0x00 +#define ARM_0_MAIL1 0x20 + +/* + * Mailbox registers. We basically only support mailbox 0 & 1. We + * deliver to the VC in mailbox 1, it delivers to us in mailbox 0. See + * BCM2835-ARM-Peripherals.pdf section 1.3 for an explanation about + * the placement of memory barriers. + */ +#define MAIL0_RD (ARM_0_MAIL0 + 0x00) +#define MAIL0_POL (ARM_0_MAIL0 + 0x10) +#define MAIL0_STA (ARM_0_MAIL0 + 0x18) +#define MAIL0_CNF (ARM_0_MAIL0 + 0x1C) +#define MAIL1_WRT (ARM_0_MAIL1 + 0x00) +#define MAIL1_STA (ARM_0_MAIL1 + 0x18) + +/* Status register: FIFO state. */ +#define ARM_MS_FULL BIT(31) +#define ARM_MS_EMPTY BIT(30) + +/* Configuration register: Enable interrupts. */ +#define ARM_MC_IHAVEDATAIRQEN BIT(0) + +struct bcm2835_mbox { + void __iomem *regs; + spinlock_t lock; + struct mbox_controller controller; +}; + +static struct bcm2835_mbox *bcm2835_link_mbox(struct mbox_chan *link) +{ + return container_of(link->mbox, struct bcm2835_mbox, controller); +} + +static irqreturn_t bcm2835_mbox_irq(int irq, void *dev_id) +{ + struct bcm2835_mbox *mbox = dev_id; + struct device *dev = mbox->controller.dev; + struct mbox_chan *link = &mbox->controller.chans[0]; + + while (!(readl(mbox->regs + MAIL0_STA) & ARM_MS_EMPTY)) { + u32 msg = readl(mbox->regs + MAIL0_RD); + dev_dbg(dev, "Reply 0x%08X\n", msg); + mbox_chan_received_data(link, &msg); + } + return IRQ_HANDLED; +} + +static int bcm2835_send_data(struct mbox_chan *link, void *data) +{ + struct bcm2835_mbox *mbox = bcm2835_link_mbox(link); + u32 msg = *(u32 *)data; + + spin_lock(&mbox->lock); + writel(msg, mbox->regs + MAIL1_WRT); + dev_dbg(mbox->controller.dev, "Request 0x%08X\n", msg); + spin_unlock(&mbox->lock); + return 0; +} + +static int bcm2835_startup(struct mbox_chan *link) +{ + struct bcm2835_mbox *mbox = bcm2835_link_mbox(link); + + /* Enable the interrupt on data reception */ + writel(ARM_MC_IHAVEDATAIRQEN, mbox->regs + MAIL0_CNF); + + return 0; +} + +static void bcm2835_shutdown(struct mbox_chan *link) +{ + struct bcm2835_mbox *mbox = bcm2835_link_mbox(link); + + writel(0, mbox->regs + MAIL0_CNF); +} + +static bool bcm2835_last_tx_done(struct mbox_chan *link) +{ + struct bcm2835_mbox *mbox = bcm2835_link_mbox(link); + bool ret; + + spin_lock(&mbox->lock); + ret = !(readl(mbox->regs + MAIL1_STA) & ARM_MS_FULL); + spin_unlock(&mbox->lock); + return ret; +} + +static const struct mbox_chan_ops bcm2835_mbox_chan_ops = { + .send_data = bcm2835_send_data, + .startup = bcm2835_startup, + .shutdown = bcm2835_shutdown, + .last_tx_done = bcm2835_last_tx_done +}; + +static struct mbox_chan *bcm2835_mbox_index_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + if (sp->args_count != 0) + return ERR_PTR(-EINVAL); + + return &mbox->chans[0]; +} + +static int bcm2835_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + void __iomem *regs; + int ret = 0; + int irq; + struct bcm2835_mbox *mbox; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (mbox == NULL) + return -ENOMEM; + + spin_lock_init(&mbox->lock); + mbox->regs = regs; + + ret = devm_request_irq(dev, irq, bcm2835_mbox_irq, + IRQF_NO_SUSPEND, dev_name(dev), mbox); + if (ret) + return ret; + + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + mbox->controller.ops = &bcm2835_mbox_chan_ops; + mbox->controller.of_xlate = &bcm2835_mbox_index_xlate; + mbox->controller.dev = dev; + mbox->controller.num_chans = 1; + mbox->controller.chans = devm_kzalloc(dev, + sizeof(*mbox->controller.chans), GFP_KERNEL); + if (!mbox->controller.chans) + return -ENOMEM; + + return devm_mbox_controller_register(dev, &mbox->controller); +} + +static const struct of_device_id bcm2835_mbox_of_match[] = { + { .compatible = "brcm,bcm2835-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_mbox_of_match); + +static struct platform_driver bcm2835_mbox_driver = { + .driver = { + .name = "bcm2835-mbox", + .of_match_table = bcm2835_mbox_of_match, + }, + .probe = bcm2835_mbox_probe, +}; +module_platform_driver(bcm2835_mbox_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("BCM2835 mailbox IPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/bcm74110-mailbox.c b/drivers/mailbox/bcm74110-mailbox.c new file mode 100644 index 000000000..c8709d509 --- /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_obj(*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 ret; + + 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 000000000..7fa979c1b --- /dev/null +++ b/drivers/mailbox/cix-mailbox.c @@ -0,0 +1,639 @@ +// 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> + +/* + * 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 overflow: 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, IRQF_NO_SUSPEND, + 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 = MBOX_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)) + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir property not found\n"); + + if (!strcmp(dir_str, "tx")) + priv->dir = 0; + else if (!strcmp(dir_str, "rx")) + priv->dir = 1; + else + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir=%s is not expected\n", dir_str); + + 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) + return dev_err_probe(dev, ret, "Failed to register mailbox\n"); + + return 0; +} + +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/cv1800-mailbox.c b/drivers/mailbox/cv1800-mailbox.c new file mode 100644 index 000000000..b062c2c4f --- /dev/null +++ b/drivers/mailbox/cv1800-mailbox.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Sophgo Technology Inc. + * Copyright (C) 2024 Yuntao Dai <d1581209858@live.com> + * Copyright (C) 2025 Junhui Liu <junhui.liu@pigmoral.tech> + */ + +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kfifo.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define RECV_CPU 1 + +#define MAILBOX_MAX_CHAN 8 +#define MAILBOX_MSG_LEN 8 + +#define MBOX_EN_REG(cpu) (cpu << 2) +#define MBOX_DONE_REG(cpu) ((cpu << 2) + 2) +#define MBOX_SET_CLR_REG(cpu) (0x10 + (cpu << 4)) +#define MBOX_SET_INT_REG(cpu) (0x18 + (cpu << 4)) +#define MBOX_SET_REG 0x60 + +#define MAILBOX_CONTEXT_OFFSET 0x0400 +#define MAILBOX_CONTEXT_SIZE 0x0040 + +#define MBOX_CONTEXT_BASE_INDEX(base, index) \ + ((u64 __iomem *)(base + MAILBOX_CONTEXT_OFFSET) + index) + +/** + * struct cv1800_mbox_chan_priv - cv1800 mailbox channel private data + * @idx: index of channel + * @cpu: send to which processor + */ +struct cv1800_mbox_chan_priv { + int idx; + int cpu; +}; + +struct cv1800_mbox { + struct mbox_controller mbox; + struct cv1800_mbox_chan_priv priv[MAILBOX_MAX_CHAN]; + struct mbox_chan chans[MAILBOX_MAX_CHAN]; + u64 __iomem *content[MAILBOX_MAX_CHAN]; + void __iomem *mbox_base; + int recvid; +}; + +static irqreturn_t cv1800_mbox_isr(int irq, void *dev_id) +{ + struct cv1800_mbox *mbox = (struct cv1800_mbox *)dev_id; + size_t i; + u64 msg; + int ret = IRQ_NONE; + + for (i = 0; i < MAILBOX_MAX_CHAN; i++) { + if (mbox->content[i] && mbox->chans[i].cl) { + memcpy_fromio(&msg, mbox->content[i], MAILBOX_MSG_LEN); + mbox->content[i] = NULL; + mbox_chan_received_data(&mbox->chans[i], (void *)&msg); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static irqreturn_t cv1800_mbox_irq(int irq, void *dev_id) +{ + struct cv1800_mbox *mbox = (struct cv1800_mbox *)dev_id; + u8 set, valid; + size_t i; + int ret = IRQ_NONE; + + set = readb(mbox->mbox_base + MBOX_SET_INT_REG(RECV_CPU)); + + if (!set) + return ret; + + for (i = 0; i < MAILBOX_MAX_CHAN; i++) { + valid = set & BIT(i); + if (valid) { + mbox->content[i] = + MBOX_CONTEXT_BASE_INDEX(mbox->mbox_base, i); + writeb(valid, mbox->mbox_base + + MBOX_SET_CLR_REG(RECV_CPU)); + writeb(~valid, mbox->mbox_base + MBOX_EN_REG(RECV_CPU)); + ret = IRQ_WAKE_THREAD; + } + } + + return ret; +} + +static int cv1800_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct cv1800_mbox_chan_priv *priv = + (struct cv1800_mbox_chan_priv *)chan->con_priv; + struct cv1800_mbox *mbox = dev_get_drvdata(chan->mbox->dev); + int idx = priv->idx; + int cpu = priv->cpu; + u8 en, valid; + + memcpy_toio(MBOX_CONTEXT_BASE_INDEX(mbox->mbox_base, idx), + data, MAILBOX_MSG_LEN); + + valid = BIT(idx); + writeb(valid, mbox->mbox_base + MBOX_SET_CLR_REG(cpu)); + en = readb(mbox->mbox_base + MBOX_EN_REG(cpu)); + writeb(en | valid, mbox->mbox_base + MBOX_EN_REG(cpu)); + writeb(valid, mbox->mbox_base + MBOX_SET_REG); + + return 0; +} + +static bool cv1800_last_tx_done(struct mbox_chan *chan) +{ + struct cv1800_mbox_chan_priv *priv = + (struct cv1800_mbox_chan_priv *)chan->con_priv; + struct cv1800_mbox *mbox = dev_get_drvdata(chan->mbox->dev); + u8 en; + + en = readb(mbox->mbox_base + MBOX_EN_REG(priv->cpu)); + + return !(en & BIT(priv->idx)); +} + +static const struct mbox_chan_ops cv1800_mbox_chan_ops = { + .send_data = cv1800_mbox_send_data, + .last_tx_done = cv1800_last_tx_done, +}; + +static struct mbox_chan *cv1800_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *spec) +{ + struct cv1800_mbox_chan_priv *priv; + + int idx = spec->args[0]; + int cpu = spec->args[1]; + + if (idx >= mbox->num_chans) + return ERR_PTR(-EINVAL); + + priv = mbox->chans[idx].con_priv; + priv->cpu = cpu; + + return &mbox->chans[idx]; +} + +static const struct of_device_id cv1800_mbox_of_match[] = { + { .compatible = "sophgo,cv1800b-mailbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cv1800_mbox_of_match); + +static int cv1800_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cv1800_mbox *mb; + int irq, idx, err; + + mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mb->mbox_base)) + return dev_err_probe(dev, PTR_ERR(mb->mbox_base), + "Failed to map resource\n"); + + mb->mbox.dev = dev; + mb->mbox.chans = mb->chans; + mb->mbox.txdone_poll = true; + mb->mbox.ops = &cv1800_mbox_chan_ops; + mb->mbox.num_chans = MAILBOX_MAX_CHAN; + mb->mbox.of_xlate = cv1800_mbox_xlate; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_threaded_irq(dev, irq, cv1800_mbox_irq, + cv1800_mbox_isr, IRQF_ONESHOT, + dev_name(&pdev->dev), mb); + if (err < 0) + return err; + + for (idx = 0; idx < MAILBOX_MAX_CHAN; idx++) { + mb->priv[idx].idx = idx; + mb->mbox.chans[idx].con_priv = &mb->priv[idx]; + } + + platform_set_drvdata(pdev, mb); + + err = devm_mbox_controller_register(dev, &mb->mbox); + if (err) + return dev_err_probe(dev, err, "Failed to register mailbox\n"); + + return 0; +} + +static struct platform_driver cv1800_mbox_driver = { + .driver = { + .name = "cv1800-mbox", + .of_match_table = cv1800_mbox_of_match, + }, + .probe = cv1800_mbox_probe, +}; + +module_platform_driver(cv1800_mbox_driver); + +MODULE_DESCRIPTION("cv1800 mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/exynos-mailbox.c b/drivers/mailbox/exynos-mailbox.c new file mode 100644 index 000000000..ff75c1c76 --- /dev/null +++ b/drivers/mailbox/exynos-mailbox.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2020 Samsung Electronics Co., Ltd. + * Copyright 2020 Google LLC. + * Copyright 2024 Linaro Ltd. + */ + +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/exynos-message.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define EXYNOS_MBOX_INTMR0 0x28 /* Interrupt Mask Register 0 */ +#define EXYNOS_MBOX_INTGR1 0x40 /* Interrupt Generation Register 1 */ + +#define EXYNOS_MBOX_INTMR0_MASK GENMASK(15, 0) +#define EXYNOS_MBOX_INTGR1_MASK GENMASK(15, 0) + +#define EXYNOS_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS_MBOX_INTGR1_MASK) + +#define EXYNOS850_MBOX_INTGR0 0x8 /* Interrupt Generation Register 0 */ +#define EXYNOS850_MBOX_INTMR1 0x24 /* Interrupt Mask Register 1 */ + +#define EXYNOS850_MBOX_INTMR1_MASK GENMASK(15, 0) +#define EXYNOS850_MBOX_INTGR0_MASK GENMASK(31, 16) + +#define EXYNOS850_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS850_MBOX_INTGR0_MASK) + +/** + * struct exynos_mbox_driver_data - platform-specific mailbox configuration. + * @intgr: offset to the IRQ generation register, doorbell + * to APM co-processor. + * @intgr_shift: shift to apply to the value written to IRQ generation + * register. + * @intmr: offset to the IRQ mask register. + * @intmr_mask: value to write to the mask register to mask out all + * interrupts. + * @num_chans: number of channels the mailbox can support (hardware + * capability). + */ +struct exynos_mbox_driver_data { + u32 intgr; + u32 intgr_shift; + u32 intmr; + u32 intmr_mask; + int num_chans; +}; + +/** + * struct exynos_mbox - driver's private data. + * @regs: mailbox registers base address. + * @mbox: pointer to the mailbox controller. + * @data: pointer to driver platform-specific data. + */ +struct exynos_mbox { + void __iomem *regs; + struct mbox_controller *mbox; + const struct exynos_mbox_driver_data *data; +}; + +static const struct exynos_mbox_driver_data exynos850_mbox_data = { + .intgr = EXYNOS850_MBOX_INTGR0, + .intgr_shift = 16, + .intmr = EXYNOS850_MBOX_INTMR1, + .intmr_mask = EXYNOS850_MBOX_INTMR1_MASK, + .num_chans = EXYNOS850_MBOX_CHAN_COUNT, +}; + +static const struct exynos_mbox_driver_data exynos_gs101_mbox_data = { + .intgr = EXYNOS_MBOX_INTGR1, + .intgr_shift = 0, + .intmr = EXYNOS_MBOX_INTMR0, + .intmr_mask = EXYNOS_MBOX_INTMR0_MASK, + .num_chans = EXYNOS_MBOX_CHAN_COUNT, +}; + +static int exynos_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct device *dev = chan->mbox->dev; + struct exynos_mbox *exynos_mbox = dev_get_drvdata(dev); + struct exynos_mbox_msg *msg = data; + + if (msg->chan_id >= exynos_mbox->mbox->num_chans) { + dev_err(dev, "Invalid channel ID %d\n", msg->chan_id); + return -EINVAL; + } + + if (msg->chan_type != EXYNOS_MBOX_CHAN_TYPE_DOORBELL) { + dev_err(dev, "Unsupported channel type [%d]\n", msg->chan_type); + return -EINVAL; + } + + /* Ring the doorbell */ + writel(BIT(msg->chan_id) << exynos_mbox->data->intgr_shift, + exynos_mbox->regs + exynos_mbox->data->intgr); + + return 0; +} + +static const struct mbox_chan_ops exynos_mbox_chan_ops = { + .send_data = exynos_mbox_send_data, +}; + +static struct mbox_chan *exynos_mbox_of_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + int i; + + if (sp->args_count != 0) + return ERR_PTR(-EINVAL); + + /* + * Return the first available channel. When we don't pass the + * channel ID from device tree, each channel populated by the driver is + * just a software construct or a virtual channel. We use 'void *data' + * in send_data() to pass the channel identifiers. + */ + for (i = 0; i < mbox->num_chans; i++) + if (mbox->chans[i].cl == NULL) + return &mbox->chans[i]; + return ERR_PTR(-EINVAL); +} + +static const struct of_device_id exynos_mbox_match[] = { + { + .compatible = "google,gs101-mbox", + .data = &exynos_gs101_mbox_data + }, + { + .compatible = "samsung,exynos850-mbox", + .data = &exynos850_mbox_data + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_mbox_match); + +static int exynos_mbox_probe(struct platform_device *pdev) +{ + const struct exynos_mbox_driver_data *data; + struct device *dev = &pdev->dev; + struct exynos_mbox *exynos_mbox; + struct mbox_controller *mbox; + struct mbox_chan *chans; + struct clk *pclk; + + data = device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + + exynos_mbox = devm_kzalloc(dev, sizeof(*exynos_mbox), GFP_KERNEL); + if (!exynos_mbox) + return -ENOMEM; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + chans = devm_kcalloc(dev, data->num_chans, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + exynos_mbox->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(exynos_mbox->regs)) + return PTR_ERR(exynos_mbox->regs); + + pclk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(pclk)) + return dev_err_probe(dev, PTR_ERR(pclk), + "Failed to enable clock.\n"); + + exynos_mbox->data = data; + mbox->num_chans = data->num_chans; + mbox->chans = chans; + mbox->dev = dev; + mbox->ops = &exynos_mbox_chan_ops; + mbox->of_xlate = exynos_mbox_of_xlate; + + exynos_mbox->mbox = mbox; + + platform_set_drvdata(pdev, exynos_mbox); + + /* Mask out all interrupts. We support just polling channels for now. */ + writel(data->intmr_mask, exynos_mbox->regs + data->intmr); + + return devm_mbox_controller_register(dev, mbox); +} + +static struct platform_driver exynos_mbox_driver = { + .probe = exynos_mbox_probe, + .driver = { + .name = "exynos-acpm-mbox", + .of_match_table = exynos_mbox_match, + }, +}; +module_platform_driver(exynos_mbox_driver); + +MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>"); +MODULE_DESCRIPTION("Samsung Exynos mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/hi3660-mailbox.c b/drivers/mailbox/hi3660-mailbox.c new file mode 100644 index 000000000..9b727a2b5 --- /dev/null +++ b/drivers/mailbox/hi3660-mailbox.c @@ -0,0 +1,296 @@ +// 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/of.h> +#include <linux/platform_device.h> +#include <linux/slab.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>"); diff --git a/drivers/mailbox/hi6220-mailbox.c b/drivers/mailbox/hi6220-mailbox.c new file mode 100644 index 000000000..8eb824220 --- /dev/null +++ b/drivers/mailbox/hi6220-mailbox.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hisilicon's Hi6220 mailbox driver + * + * Copyright (c) 2015 HiSilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan <leo.yan@linaro.org> + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kfifo.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define MBOX_CHAN_MAX 32 + +#define MBOX_TX 0x1 + +/* Mailbox message length: 8 words */ +#define MBOX_MSG_LEN 8 + +/* Mailbox Registers */ +#define MBOX_OFF(m) (0x40 * (m)) +#define MBOX_MODE_REG(m) (MBOX_OFF(m) + 0x0) +#define MBOX_DATA_REG(m) (MBOX_OFF(m) + 0x4) + +#define MBOX_STATE_MASK (0xF << 4) +#define MBOX_STATE_IDLE (0x1 << 4) +#define MBOX_STATE_TX (0x2 << 4) +#define MBOX_STATE_RX (0x4 << 4) +#define MBOX_STATE_ACK (0x8 << 4) +#define MBOX_ACK_CONFIG_MASK (0x1 << 0) +#define MBOX_ACK_AUTOMATIC (0x1 << 0) +#define MBOX_ACK_IRQ (0x0 << 0) + +/* IPC registers */ +#define ACK_INT_RAW_REG(i) ((i) + 0x400) +#define ACK_INT_MSK_REG(i) ((i) + 0x404) +#define ACK_INT_STAT_REG(i) ((i) + 0x408) +#define ACK_INT_CLR_REG(i) ((i) + 0x40c) +#define ACK_INT_ENA_REG(i) ((i) + 0x500) +#define ACK_INT_DIS_REG(i) ((i) + 0x504) +#define DST_INT_RAW_REG(i) ((i) + 0x420) + + +struct hi6220_mbox_chan { + + /* + * Description for channel's hardware info: + * - direction: tx or rx + * - dst irq: peer core's irq number + * - ack irq: local irq number + * - slot number + */ + unsigned int dir, dst_irq, ack_irq; + unsigned int slot; + + struct hi6220_mbox *parent; +}; + +struct hi6220_mbox { + struct device *dev; + + int irq; + + /* flag of enabling tx's irq mode */ + bool tx_irq_mode; + + /* region for ipc event */ + void __iomem *ipc; + + /* region for mailbox */ + void __iomem *base; + + void *irq_map_chan[MBOX_CHAN_MAX]; + struct mbox_chan *chan; + struct mbox_controller controller; + + unsigned int chan_num; + struct hi6220_mbox_chan mchan[] __counted_by(chan_num); +}; + +static void mbox_set_state(struct hi6220_mbox *mbox, + unsigned int slot, u32 val) +{ + u32 status; + + status = readl(mbox->base + MBOX_MODE_REG(slot)); + status = (status & ~MBOX_STATE_MASK) | val; + writel(status, mbox->base + MBOX_MODE_REG(slot)); +} + +static void mbox_set_mode(struct hi6220_mbox *mbox, + unsigned int slot, u32 val) +{ + u32 mode; + + mode = readl(mbox->base + MBOX_MODE_REG(slot)); + mode = (mode & ~MBOX_ACK_CONFIG_MASK) | val; + writel(mode, mbox->base + MBOX_MODE_REG(slot)); +} + +static bool hi6220_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + u32 state; + + /* Only set idle state for polling mode */ + BUG_ON(mbox->tx_irq_mode); + + state = readl(mbox->base + MBOX_MODE_REG(mchan->slot)); + return ((state & MBOX_STATE_MASK) == MBOX_STATE_IDLE); +} + +static int hi6220_mbox_send_data(struct mbox_chan *chan, void *msg) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + unsigned int slot = mchan->slot; + u32 *buf = msg; + int i; + + /* indicate as a TX channel */ + mchan->dir = MBOX_TX; + + mbox_set_state(mbox, slot, MBOX_STATE_TX); + + if (mbox->tx_irq_mode) + mbox_set_mode(mbox, slot, MBOX_ACK_IRQ); + else + mbox_set_mode(mbox, slot, MBOX_ACK_AUTOMATIC); + + for (i = 0; i < MBOX_MSG_LEN; i++) + writel(buf[i], mbox->base + MBOX_DATA_REG(slot) + i * 4); + + /* trigger remote request */ + writel(BIT(mchan->dst_irq), DST_INT_RAW_REG(mbox->ipc)); + return 0; +} + +static irqreturn_t hi6220_mbox_interrupt(int irq, void *p) +{ + struct hi6220_mbox *mbox = p; + struct hi6220_mbox_chan *mchan; + struct mbox_chan *chan; + unsigned int state, intr_bit, i; + u32 msg[MBOX_MSG_LEN]; + + state = readl(ACK_INT_STAT_REG(mbox->ipc)); + if (!state) { + dev_warn(mbox->dev, "%s: spurious interrupt\n", + __func__); + return IRQ_HANDLED; + } + + while (state) { + intr_bit = __ffs(state); + state &= (state - 1); + + chan = mbox->irq_map_chan[intr_bit]; + if (!chan) { + dev_warn(mbox->dev, "%s: unexpected irq vector %d\n", + __func__, intr_bit); + continue; + } + + mchan = chan->con_priv; + if (mchan->dir == MBOX_TX) + mbox_chan_txdone(chan, 0); + else { + for (i = 0; i < MBOX_MSG_LEN; i++) + msg[i] = readl(mbox->base + + MBOX_DATA_REG(mchan->slot) + i * 4); + + mbox_chan_received_data(chan, (void *)msg); + } + + /* clear IRQ source */ + writel(BIT(mchan->ack_irq), ACK_INT_CLR_REG(mbox->ipc)); + mbox_set_state(mbox, mchan->slot, MBOX_STATE_IDLE); + } + + return IRQ_HANDLED; +} + +static int hi6220_mbox_startup(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + + mchan->dir = 0; + + /* enable interrupt */ + writel(BIT(mchan->ack_irq), ACK_INT_ENA_REG(mbox->ipc)); + return 0; +} + +static void hi6220_mbox_shutdown(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + + /* disable interrupt */ + writel(BIT(mchan->ack_irq), ACK_INT_DIS_REG(mbox->ipc)); + mbox->irq_map_chan[mchan->ack_irq] = NULL; +} + +static const struct mbox_chan_ops hi6220_mbox_ops = { + .send_data = hi6220_mbox_send_data, + .startup = hi6220_mbox_startup, + .shutdown = hi6220_mbox_shutdown, + .last_tx_done = hi6220_mbox_last_tx_done, +}; + +static struct mbox_chan *hi6220_mbox_xlate(struct mbox_controller *controller, + const struct of_phandle_args *spec) +{ + struct hi6220_mbox *mbox = dev_get_drvdata(controller->dev); + struct hi6220_mbox_chan *mchan; + struct mbox_chan *chan; + unsigned int i = spec->args[0]; + unsigned int dst_irq = spec->args[1]; + unsigned int ack_irq = spec->args[2]; + + /* Bounds checking */ + if (i >= mbox->chan_num || dst_irq >= mbox->chan_num || + ack_irq >= mbox->chan_num) { + dev_err(mbox->dev, + "Invalid channel idx %d dst_irq %d ack_irq %d\n", + i, dst_irq, ack_irq); + return ERR_PTR(-EINVAL); + } + + /* Is requested channel free? */ + chan = &mbox->chan[i]; + if (mbox->irq_map_chan[ack_irq] == (void *)chan) { + dev_err(mbox->dev, "Channel in use\n"); + return ERR_PTR(-EBUSY); + } + + mchan = chan->con_priv; + mchan->dst_irq = dst_irq; + mchan->ack_irq = ack_irq; + + mbox->irq_map_chan[ack_irq] = (void *)chan; + return chan; +} + +static const struct of_device_id hi6220_mbox_of_match[] = { + { .compatible = "hisilicon,hi6220-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi6220_mbox_of_match); + +static int hi6220_mbox_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct hi6220_mbox *mbox; + int i, err; + + mbox = devm_kzalloc(dev, struct_size(mbox, mchan, MBOX_CHAN_MAX), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + mbox->chan_num = MBOX_CHAN_MAX; + mbox->dev = dev; + + mbox->chan = devm_kcalloc(dev, + mbox->chan_num, sizeof(*mbox->chan), GFP_KERNEL); + if (!mbox->chan) + return -ENOMEM; + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq < 0) + return mbox->irq; + + mbox->ipc = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->ipc)) { + dev_err(dev, "ioremap ipc failed\n"); + return PTR_ERR(mbox->ipc); + } + + mbox->base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(mbox->base)) { + dev_err(dev, "ioremap buffer failed\n"); + return PTR_ERR(mbox->base); + } + + err = devm_request_irq(dev, mbox->irq, hi6220_mbox_interrupt, 0, + dev_name(dev), mbox); + if (err) + return -ENODEV; + + mbox->controller.dev = dev; + mbox->controller.chans = &mbox->chan[0]; + mbox->controller.num_chans = mbox->chan_num; + mbox->controller.ops = &hi6220_mbox_ops; + mbox->controller.of_xlate = hi6220_mbox_xlate; + + for (i = 0; i < mbox->chan_num; i++) { + mbox->chan[i].con_priv = &mbox->mchan[i]; + mbox->irq_map_chan[i] = NULL; + + mbox->mchan[i].parent = mbox; + mbox->mchan[i].slot = i; + } + + /* mask and clear all interrupt vectors */ + writel(0x0, ACK_INT_MSK_REG(mbox->ipc)); + writel(~0x0, ACK_INT_CLR_REG(mbox->ipc)); + + /* use interrupt for tx's ack */ + mbox->tx_irq_mode = !of_property_read_bool(node, "hi6220,mbox-tx-noirq"); + + if (mbox->tx_irq_mode) + mbox->controller.txdone_irq = true; + else { + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + } + + 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 hi6220_mbox_driver = { + .driver = { + .name = "hi6220-mbox", + .of_match_table = hi6220_mbox_of_match, + }, + .probe = hi6220_mbox_probe, +}; + +static int __init hi6220_mbox_init(void) +{ + return platform_driver_register(&hi6220_mbox_driver); +} +core_initcall(hi6220_mbox_init); + +static void __exit hi6220_mbox_exit(void) +{ + platform_driver_unregister(&hi6220_mbox_driver); +} +module_exit(hi6220_mbox_exit); + +MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>"); +MODULE_DESCRIPTION("Hi6220 mailbox driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/imx-mailbox.c b/drivers/mailbox/imx-mailbox.c new file mode 100644 index 000000000..40bad2b3e --- /dev/null +++ b/drivers/mailbox/imx-mailbox.c @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de> + * Copyright 2022 NXP, Peng Fan <peng.fan@nxp.com> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/firmware/imx/ipc.h> +#include <linux/firmware/imx/s4.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/suspend.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define IMX_MU_CHANS 24 +/* TX0/RX0/RXDB[0-3] */ +#define IMX_MU_SCU_CHANS 6 +/* TX0/RX0 */ +#define IMX_MU_S4_CHANS 2 +#define IMX_MU_CHAN_NAME_SIZE 32 + +#define IMX_MU_V2_PAR_OFF 0x4 +#define IMX_MU_V2_TR_MASK GENMASK(7, 0) +#define IMX_MU_V2_RR_MASK GENMASK(15, 8) + +#define IMX_MU_SECO_TX_TOUT (msecs_to_jiffies(3000)) +#define IMX_MU_SECO_RX_TOUT (msecs_to_jiffies(3000)) + +/* Please not change TX & RX */ +enum imx_mu_chan_type { + IMX_MU_TYPE_TX = 0, /* Tx */ + IMX_MU_TYPE_RX = 1, /* Rx */ + IMX_MU_TYPE_TXDB = 2, /* Tx doorbell */ + IMX_MU_TYPE_RXDB = 3, /* Rx doorbell */ + IMX_MU_TYPE_RST = 4, /* Reset */ + IMX_MU_TYPE_TXDB_V2 = 5, /* Tx doorbell with S/W ACK */ +}; + +enum imx_mu_xcr { + IMX_MU_CR, + IMX_MU_GIER, + IMX_MU_GCR, + IMX_MU_TCR, + IMX_MU_RCR, + IMX_MU_xCR_MAX, +}; + +enum imx_mu_xsr { + IMX_MU_SR, + IMX_MU_GSR, + IMX_MU_TSR, + IMX_MU_RSR, + IMX_MU_xSR_MAX, +}; + +struct imx_sc_rpc_msg_max { + struct imx_sc_rpc_msg hdr; + u32 data[30]; +}; + +struct imx_s4_rpc_msg_max { + struct imx_s4_rpc_msg hdr; + u32 data[254]; +}; + +struct imx_mu_con_priv { + unsigned int idx; + char irq_desc[IMX_MU_CHAN_NAME_SIZE]; + enum imx_mu_chan_type type; + struct mbox_chan *chan; + struct work_struct txdb_work; + bool shutdown; +}; + +struct imx_mu_priv { + struct device *dev; + void __iomem *base; + void *msg; + raw_spinlock_t xcr_lock; /* control register lock */ + + struct mbox_controller mbox; + struct mbox_chan mbox_chans[IMX_MU_CHANS]; + + struct imx_mu_con_priv con_priv[IMX_MU_CHANS]; + const struct imx_mu_dcfg *dcfg; + struct clk *clk; + int irq[IMX_MU_CHANS]; + bool suspend; + bool side_b; + + u32 xcr[IMX_MU_xCR_MAX]; + u32 num_tr; + u32 num_rr; +}; + +enum imx_mu_type { + IMX_MU_V1, + IMX_MU_V2 = BIT(1), + IMX_MU_V2_S4 = BIT(15), + IMX_MU_V2_IRQ = BIT(16), +}; + +struct imx_mu_dcfg { + int (*tx)(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, void *data); + int (*rx)(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp); + int (*rxdb)(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp); + int (*init)(struct imx_mu_priv *priv); + enum imx_mu_type type; + u32 xTR; /* Transmit Register0 */ + u32 xRR; /* Receive Register0 */ + u32 xSR[IMX_MU_xSR_MAX]; /* Status Registers */ + u32 xCR[IMX_MU_xCR_MAX]; /* Control Registers */ + bool skip_suspend_flag; +}; + +#define IMX_MU_xSR_GIPn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(28 + (3 - (x)))) +#define IMX_MU_xSR_RFn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(24 + (3 - (x)))) +#define IMX_MU_xSR_TEn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(20 + (3 - (x)))) + +/* General Purpose Interrupt Enable */ +#define IMX_MU_xCR_GIEn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(28 + (3 - (x)))) +/* Receive Interrupt Enable */ +#define IMX_MU_xCR_RIEn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(24 + (3 - (x)))) +/* Transmit Interrupt Enable */ +#define IMX_MU_xCR_TIEn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(20 + (3 - (x)))) +/* General Purpose Interrupt Request */ +#define IMX_MU_xCR_GIRn(type, x) (type & IMX_MU_V2 ? BIT(x) : BIT(16 + (3 - (x)))) +/* MU reset */ +#define IMX_MU_xCR_RST(type) (type & IMX_MU_V2 ? BIT(0) : BIT(5)) +#define IMX_MU_xSR_RST(type) (type & IMX_MU_V2 ? BIT(0) : BIT(7)) + + +static struct imx_mu_priv *to_imx_mu_priv(struct mbox_controller *mbox) +{ + return container_of(mbox, struct imx_mu_priv, mbox); +} + +static void imx_mu_write(struct imx_mu_priv *priv, u32 val, u32 offs) +{ + iowrite32(val, priv->base + offs); +} + +static u32 imx_mu_read(struct imx_mu_priv *priv, u32 offs) +{ + return ioread32(priv->base + offs); +} + +static int imx_mu_tx_waiting_write(struct imx_mu_priv *priv, u32 val, u32 idx) +{ + u64 timeout_time = get_jiffies_64() + IMX_MU_SECO_TX_TOUT; + u32 status; + u32 can_write; + + dev_dbg(priv->dev, "Trying to write %.8x to idx %d\n", val, idx); + + do { + status = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_TSR]); + can_write = status & IMX_MU_xSR_TEn(priv->dcfg->type, idx % 4); + } while (!can_write && time_is_after_jiffies64(timeout_time)); + + if (!can_write) { + dev_err(priv->dev, "timeout trying to write %.8x at %d(%.8x)\n", + val, idx, status); + return -ETIME; + } + + imx_mu_write(priv, val, priv->dcfg->xTR + (idx % 4) * 4); + + return 0; +} + +static int imx_mu_rx_waiting_read(struct imx_mu_priv *priv, u32 *val, u32 idx) +{ + u64 timeout_time = get_jiffies_64() + IMX_MU_SECO_RX_TOUT; + u32 status; + u32 can_read; + + dev_dbg(priv->dev, "Trying to read from idx %d\n", idx); + + do { + status = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_RSR]); + can_read = status & IMX_MU_xSR_RFn(priv->dcfg->type, idx % 4); + } while (!can_read && time_is_after_jiffies64(timeout_time)); + + if (!can_read) { + dev_err(priv->dev, "timeout trying to read idx %d (%.8x)\n", + idx, status); + return -ETIME; + } + + *val = imx_mu_read(priv, priv->dcfg->xRR + (idx % 4) * 4); + dev_dbg(priv->dev, "Read %.8x\n", *val); + + return 0; +} + +static u32 imx_mu_xcr_rmw(struct imx_mu_priv *priv, enum imx_mu_xcr type, u32 set, u32 clr) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&priv->xcr_lock); + + val = imx_mu_read(priv, priv->dcfg->xCR[type]); + val &= ~clr; + val |= set; + imx_mu_write(priv, val, priv->dcfg->xCR[type]); + + return val; +} + +static void imx_mu_xcr_clr_shut(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, + enum imx_mu_xcr type, u32 clr) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&priv->xcr_lock); + cp->shutdown = true; + + val = imx_mu_read(priv, priv->dcfg->xCR[type]); + val &= ~clr; + imx_mu_write(priv, val, priv->dcfg->xCR[type]); +} + +static void imx_mu_xcr_set_act(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, + enum imx_mu_xcr type, u32 set) +{ + u32 val; + + guard(raw_spinlock_irqsave)(&priv->xcr_lock); + if (!cp->shutdown) { + val = imx_mu_read(priv, priv->dcfg->xCR[type]); + val |= set; + imx_mu_write(priv, val, priv->dcfg->xCR[type]); + } +} + +static int imx_mu_generic_tx(struct imx_mu_priv *priv, + struct imx_mu_con_priv *cp, + void *data) +{ + u32 *arg = data; + u32 val; + int ret, count; + + ret = 0; + switch (cp->type) { + case IMX_MU_TYPE_TX: + imx_mu_write(priv, *arg, priv->dcfg->xTR + cp->idx * 4); + imx_mu_xcr_rmw(priv, IMX_MU_TCR, IMX_MU_xCR_TIEn(priv->dcfg->type, cp->idx), 0); + break; + case IMX_MU_TYPE_TXDB: + imx_mu_xcr_rmw(priv, IMX_MU_GCR, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), 0); + queue_work(system_bh_wq, &cp->txdb_work); + break; + case IMX_MU_TYPE_TXDB_V2: + imx_mu_write(priv, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), + priv->dcfg->xCR[IMX_MU_GCR]); + ret = -ETIMEDOUT; + count = 0; + while (ret && (count < 10)) { + ret = + readl_poll_timeout(priv->base + priv->dcfg->xCR[IMX_MU_GCR], val, + !(val & IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx)), + 0, 10000); + + if (ret) { + dev_warn_ratelimited(priv->dev, + "channel type: %d timeout, %d times, retry\n", + cp->type, ++count); + } + } + break; + default: + dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type); + return -EINVAL; + } + + return ret; +} + +static int imx_mu_generic_rx(struct imx_mu_priv *priv, + struct imx_mu_con_priv *cp) +{ + u32 dat; + + dat = imx_mu_read(priv, priv->dcfg->xRR + (cp->idx) * 4); + mbox_chan_received_data(cp->chan, (void *)&dat); + + return 0; +} + +static int imx_mu_generic_rxdb(struct imx_mu_priv *priv, + struct imx_mu_con_priv *cp) +{ + imx_mu_write(priv, IMX_MU_xSR_GIPn(priv->dcfg->type, cp->idx), + priv->dcfg->xSR[IMX_MU_GSR]); + mbox_chan_received_data(cp->chan, NULL); + + return 0; +} + +static int imx_mu_specific_tx(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, void *data) +{ + u32 *arg = data; + u32 num_tr = priv->num_tr; + int i, ret; + u32 xsr; + u32 size, max_size; + + if (priv->dcfg->type & IMX_MU_V2_S4) { + size = ((struct imx_s4_rpc_msg_max *)data)->hdr.size; + max_size = sizeof(struct imx_s4_rpc_msg_max); + } else { + size = ((struct imx_sc_rpc_msg_max *)data)->hdr.size; + max_size = sizeof(struct imx_sc_rpc_msg_max); + } + + switch (cp->type) { + case IMX_MU_TYPE_TX: + /* + * msg->hdr.size specifies the number of u32 words while + * sizeof yields bytes. + */ + + if (size > max_size / 4) { + /* + * The real message size can be different to + * struct imx_sc_rpc_msg_max/imx_s4_rpc_msg_max size + */ + dev_err(priv->dev, "Maximal message size (%u bytes) exceeded on TX; got: %i bytes\n", max_size, size << 2); + return -EINVAL; + } + + for (i = 0; i < num_tr && i < size; i++) + imx_mu_write(priv, *arg++, priv->dcfg->xTR + (i % num_tr) * 4); + for (; i < size; i++) { + ret = readl_poll_timeout(priv->base + priv->dcfg->xSR[IMX_MU_TSR], + xsr, + xsr & IMX_MU_xSR_TEn(priv->dcfg->type, i % num_tr), + 0, 5 * USEC_PER_SEC); + if (ret) { + dev_err(priv->dev, "Send data index: %d timeout\n", i); + return ret; + } + imx_mu_write(priv, *arg++, priv->dcfg->xTR + (i % num_tr) * 4); + } + + imx_mu_xcr_rmw(priv, IMX_MU_TCR, IMX_MU_xCR_TIEn(priv->dcfg->type, cp->idx), 0); + break; + default: + dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type); + return -EINVAL; + } + + return 0; +} + +static int imx_mu_specific_rx(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp) +{ + u32 *data; + int i, ret; + u32 xsr; + u32 size, max_size; + u32 num_rr = priv->num_rr; + + data = (u32 *)priv->msg; + + *data++ = imx_mu_read(priv, priv->dcfg->xRR); + + if (priv->dcfg->type & IMX_MU_V2_S4) { + size = ((struct imx_s4_rpc_msg_max *)priv->msg)->hdr.size; + max_size = sizeof(struct imx_s4_rpc_msg_max); + } else { + size = ((struct imx_sc_rpc_msg_max *)priv->msg)->hdr.size; + max_size = sizeof(struct imx_sc_rpc_msg_max); + } + + if (size > max_size / 4) { + dev_err(priv->dev, "Maximal message size (%u bytes) exceeded on RX; got: %i bytes\n", max_size, size << 2); + return -EINVAL; + } + + for (i = 1; i < size; i++) { + ret = readl_poll_timeout(priv->base + priv->dcfg->xSR[IMX_MU_RSR], xsr, + xsr & IMX_MU_xSR_RFn(priv->dcfg->type, i % num_rr), 0, + 5 * USEC_PER_SEC); + if (ret) { + dev_err(priv->dev, "timeout read idx %d\n", i); + return ret; + } + *data++ = imx_mu_read(priv, priv->dcfg->xRR + (i % num_rr) * 4); + } + + mbox_chan_received_data(cp->chan, (void *)priv->msg); + + return 0; +} + +static int imx_mu_seco_tx(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp, + void *data) +{ + struct imx_sc_rpc_msg_max *msg = data; + u32 *arg = data; + u32 byte_size; + int err; + int i; + + dev_dbg(priv->dev, "Sending message\n"); + + switch (cp->type) { + case IMX_MU_TYPE_TXDB: + byte_size = msg->hdr.size * sizeof(u32); + if (byte_size > sizeof(*msg)) { + /* + * The real message size can be different to + * struct imx_sc_rpc_msg_max size + */ + dev_err(priv->dev, + "Exceed max msg size (%zu) on TX, got: %i\n", + sizeof(*msg), byte_size); + return -EINVAL; + } + + print_hex_dump_debug("from client ", DUMP_PREFIX_OFFSET, 4, 4, + data, byte_size, false); + + /* Send first word */ + dev_dbg(priv->dev, "Sending header\n"); + imx_mu_write(priv, *arg++, priv->dcfg->xTR); + + /* Send signaling */ + dev_dbg(priv->dev, "Sending signaling\n"); + imx_mu_xcr_rmw(priv, IMX_MU_GCR, + IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), 0); + + /* Send words to fill the mailbox */ + for (i = 1; i < 4 && i < msg->hdr.size; i++) { + dev_dbg(priv->dev, "Sending word %d\n", i); + imx_mu_write(priv, *arg++, + priv->dcfg->xTR + (i % 4) * 4); + } + + /* Send rest of message waiting for remote read */ + for (; i < msg->hdr.size; i++) { + dev_dbg(priv->dev, "Sending word %d\n", i); + err = imx_mu_tx_waiting_write(priv, *arg++, i); + if (err) { + dev_err(priv->dev, "Timeout tx %d\n", i); + return err; + } + } + + /* Simulate hack for mbox framework */ + queue_work(system_bh_wq, &cp->txdb_work); + + break; + default: + dev_warn_ratelimited(priv->dev, + "Send data on wrong channel type: %d\n", + cp->type); + return -EINVAL; + } + + return 0; +} + +static int imx_mu_seco_rxdb(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp) +{ + struct imx_sc_rpc_msg_max msg; + u32 *data = (u32 *)&msg; + u32 byte_size; + int err = 0; + int i; + + dev_dbg(priv->dev, "Receiving message\n"); + + /* Read header */ + dev_dbg(priv->dev, "Receiving header\n"); + *data++ = imx_mu_read(priv, priv->dcfg->xRR); + byte_size = msg.hdr.size * sizeof(u32); + if (byte_size > sizeof(msg)) { + dev_err(priv->dev, "Exceed max msg size (%zu) on RX, got: %i\n", + sizeof(msg), byte_size); + err = -EINVAL; + goto error; + } + + /* Read message waiting they are written */ + for (i = 1; i < msg.hdr.size; i++) { + dev_dbg(priv->dev, "Receiving word %d\n", i); + err = imx_mu_rx_waiting_read(priv, data++, i); + if (err) { + dev_err(priv->dev, "Timeout rx %d\n", i); + goto error; + } + } + + /* Clear GIP */ + imx_mu_write(priv, IMX_MU_xSR_GIPn(priv->dcfg->type, cp->idx), + priv->dcfg->xSR[IMX_MU_GSR]); + + print_hex_dump_debug("to client ", DUMP_PREFIX_OFFSET, 4, 4, + &msg, byte_size, false); + + /* send data to client */ + dev_dbg(priv->dev, "Sending message to client\n"); + mbox_chan_received_data(cp->chan, (void *)&msg); + + goto exit; + +error: + mbox_chan_received_data(cp->chan, ERR_PTR(err)); + +exit: + return err; +} + +static void imx_mu_txdb_work(struct work_struct *t) +{ + struct imx_mu_con_priv *cp = from_work(cp, t, txdb_work); + + mbox_chan_txdone(cp->chan, 0); +} + +static irqreturn_t imx_mu_isr_th(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); + struct imx_mu_con_priv *cp = chan->con_priv; + + switch (cp->type) { + case IMX_MU_TYPE_TX: + mbox_chan_txdone(chan, 0); + break; + + case IMX_MU_TYPE_RX: + if (!priv->dcfg->rx(priv, cp)) + imx_mu_xcr_set_act(priv, cp, IMX_MU_RCR, IMX_MU_xCR_RIEn(priv->dcfg->type, cp->idx)); + break; + + case IMX_MU_TYPE_RXDB: + if (!priv->dcfg->rxdb(priv, cp)) + imx_mu_xcr_set_act(priv, cp, IMX_MU_GIER, IMX_MU_xCR_GIEn(priv->dcfg->type, cp->idx)); + break; + + default: + dev_warn_ratelimited(priv->dev, "Unhandled channel type %d\n", + cp->type); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static irqreturn_t imx_mu_isr(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); + struct imx_mu_con_priv *cp = chan->con_priv; + irqreturn_t ret = IRQ_HANDLED; + u32 val, ctrl; + + switch (cp->type) { + case IMX_MU_TYPE_TX: + ctrl = imx_mu_read(priv, priv->dcfg->xCR[IMX_MU_TCR]); + val = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_TSR]); + val &= IMX_MU_xSR_TEn(priv->dcfg->type, cp->idx) & + (ctrl & IMX_MU_xCR_TIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RX: + ctrl = imx_mu_read(priv, priv->dcfg->xCR[IMX_MU_RCR]); + val = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_RSR]); + val &= IMX_MU_xSR_RFn(priv->dcfg->type, cp->idx) & + (ctrl & IMX_MU_xCR_RIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RXDB: + ctrl = imx_mu_read(priv, priv->dcfg->xCR[IMX_MU_GIER]); + val = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_GSR]); + val &= IMX_MU_xSR_GIPn(priv->dcfg->type, cp->idx) & + (ctrl & IMX_MU_xCR_GIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RST: + return IRQ_NONE; + default: + dev_warn_ratelimited(priv->dev, "Unhandled channel type %d\n", + cp->type); + return IRQ_NONE; + } + + if (!val) + return IRQ_NONE; + + if ((val == IMX_MU_xSR_TEn(priv->dcfg->type, cp->idx)) && + (cp->type == IMX_MU_TYPE_TX)) { + imx_mu_xcr_rmw(priv, IMX_MU_TCR, 0, IMX_MU_xCR_TIEn(priv->dcfg->type, cp->idx)); + ret = IRQ_WAKE_THREAD; + } else if ((val == IMX_MU_xSR_RFn(priv->dcfg->type, cp->idx)) && + (cp->type == IMX_MU_TYPE_RX)) { + imx_mu_xcr_rmw(priv, IMX_MU_RCR, 0, IMX_MU_xCR_RIEn(priv->dcfg->type, cp->idx)); + ret = IRQ_WAKE_THREAD; + } else if ((val == IMX_MU_xSR_GIPn(priv->dcfg->type, cp->idx)) && + (cp->type == IMX_MU_TYPE_RXDB)) { + imx_mu_xcr_rmw(priv, IMX_MU_GIER, 0, IMX_MU_xCR_GIEn(priv->dcfg->type, cp->idx)); + ret = IRQ_WAKE_THREAD; + } else { + dev_warn_ratelimited(priv->dev, "Not handled interrupt\n"); + return IRQ_NONE; + } + + if (priv->suspend) + pm_system_wakeup(); + + return ret; +} + +static int imx_mu_send_data(struct mbox_chan *chan, void *data) +{ + struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); + struct imx_mu_con_priv *cp = chan->con_priv; + + return priv->dcfg->tx(priv, cp, data); +} + +static int imx_mu_startup(struct mbox_chan *chan) +{ + struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); + struct imx_mu_con_priv *cp = chan->con_priv; + unsigned long irq_flag = IRQF_NO_THREAD; + int ret; + + pm_runtime_get_sync(priv->dev); + if (cp->type == IMX_MU_TYPE_TXDB_V2) + return 0; + + if (cp->type == IMX_MU_TYPE_TXDB) { + /* Tx doorbell don't have ACK support */ + INIT_WORK(&cp->txdb_work, imx_mu_txdb_work); + return 0; + } + + /* IPC MU should be with IRQF_NO_SUSPEND set */ + if (!priv->dev->pm_domain) + irq_flag |= IRQF_NO_SUSPEND; + + if (!(priv->dcfg->type & IMX_MU_V2_IRQ)) + irq_flag |= IRQF_SHARED; + + ret = request_threaded_irq(priv->irq[cp->type], imx_mu_isr, imx_mu_isr_th, + irq_flag, cp->irq_desc, chan); + if (ret) { + dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq[cp->type]); + return ret; + } + + cp->shutdown = false; + switch (cp->type) { + case IMX_MU_TYPE_RX: + imx_mu_xcr_rmw(priv, IMX_MU_RCR, IMX_MU_xCR_RIEn(priv->dcfg->type, cp->idx), 0); + break; + case IMX_MU_TYPE_RXDB: + imx_mu_xcr_rmw(priv, IMX_MU_GIER, IMX_MU_xCR_GIEn(priv->dcfg->type, cp->idx), 0); + break; + default: + break; + } + + return 0; +} + +static void imx_mu_shutdown(struct mbox_chan *chan) +{ + struct imx_mu_priv *priv = to_imx_mu_priv(chan->mbox); + struct imx_mu_con_priv *cp = chan->con_priv; + int ret; + u32 sr; + + if (cp->type == IMX_MU_TYPE_TXDB_V2) { + pm_runtime_put_sync(priv->dev); + return; + } + + if (cp->type == IMX_MU_TYPE_TXDB) { + cancel_work_sync(&cp->txdb_work); + pm_runtime_put_sync(priv->dev); + return; + } + + switch (cp->type) { + case IMX_MU_TYPE_TX: + imx_mu_xcr_clr_shut(priv, cp, IMX_MU_TCR, IMX_MU_xCR_TIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RX: + imx_mu_xcr_clr_shut(priv, cp, IMX_MU_RCR, IMX_MU_xCR_RIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RXDB: + imx_mu_xcr_clr_shut(priv, cp, IMX_MU_GIER, IMX_MU_xCR_GIEn(priv->dcfg->type, cp->idx)); + break; + case IMX_MU_TYPE_RST: + imx_mu_xcr_rmw(priv, IMX_MU_CR, IMX_MU_xCR_RST(priv->dcfg->type), 0); + ret = readl_poll_timeout(priv->base + priv->dcfg->xSR[IMX_MU_SR], sr, + !(sr & IMX_MU_xSR_RST(priv->dcfg->type)), 1, 5); + if (ret) + dev_warn(priv->dev, "RST channel timeout\n"); + break; + default: + break; + } + + free_irq(priv->irq[cp->type], chan); + pm_runtime_put_sync(priv->dev); +} + +static const struct mbox_chan_ops imx_mu_ops = { + .send_data = imx_mu_send_data, + .startup = imx_mu_startup, + .shutdown = imx_mu_shutdown, +}; + +static struct mbox_chan *imx_mu_specific_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + u32 type, idx, chan; + + if (sp->args_count != 2) { + dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count); + return ERR_PTR(-EINVAL); + } + + type = sp->args[0]; /* channel type */ + idx = sp->args[1]; /* index */ + + switch (type) { + case IMX_MU_TYPE_TX: + case IMX_MU_TYPE_RX: + if (idx != 0) + dev_err(mbox->dev, "Invalid chan idx: %d\n", idx); + chan = type; + break; + case IMX_MU_TYPE_RXDB: + chan = 2 + idx; + break; + default: + dev_err(mbox->dev, "Invalid chan type: %d\n", type); + return ERR_PTR(-EINVAL); + } + + if (chan >= mbox->num_chans) { + dev_err(mbox->dev, "Not supported channel number: %d. (type: %d, idx: %d)\n", chan, type, idx); + return ERR_PTR(-EINVAL); + } + + return &mbox->chans[chan]; +} + +static struct mbox_chan * imx_mu_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + struct mbox_chan *p_chan; + u32 type, idx, chan; + + if (sp->args_count != 2) { + dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count); + return ERR_PTR(-EINVAL); + } + + type = sp->args[0]; /* channel type */ + idx = sp->args[1]; /* index */ + + /* RST only supports 1 channel */ + if ((type == IMX_MU_TYPE_RST) && idx) { + dev_err(mbox->dev, "Invalid RST channel %d\n", idx); + return ERR_PTR(-EINVAL); + } + + chan = type * 4 + idx; + if (chan >= mbox->num_chans) { + dev_err(mbox->dev, "Not supported channel number: %d. (type: %d, idx: %d)\n", chan, type, idx); + return ERR_PTR(-EINVAL); + } + + p_chan = &mbox->chans[chan]; + + if (type == IMX_MU_TYPE_TXDB_V2) + p_chan->txdone_method = MBOX_TXDONE_BY_ACK; + + return p_chan; +} + +static struct mbox_chan *imx_mu_seco_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + u32 type; + + if (sp->args_count < 1) { + dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count); + return ERR_PTR(-EINVAL); + } + + type = sp->args[0]; /* channel type */ + + /* Only supports TXDB and RXDB */ + if (type == IMX_MU_TYPE_TX || type == IMX_MU_TYPE_RX) { + dev_err(mbox->dev, "Invalid type: %d\n", type); + return ERR_PTR(-EINVAL); + } + + return imx_mu_xlate(mbox, sp); +} + +static void imx_mu_get_tr_rr(struct imx_mu_priv *priv) +{ + u32 val; + + if (priv->dcfg->type & IMX_MU_V2) { + val = imx_mu_read(priv, IMX_MU_V2_PAR_OFF); + priv->num_tr = FIELD_GET(IMX_MU_V2_TR_MASK, val); + priv->num_rr = FIELD_GET(IMX_MU_V2_RR_MASK, val); + } else { + priv->num_tr = 4; + priv->num_rr = 4; + } +} + +static int imx_mu_init_generic(struct imx_mu_priv *priv) +{ + unsigned int i; + unsigned int val; + + if (priv->num_rr > 4 || priv->num_tr > 4) { + WARN_ONCE(true, "%s not support TR/RR larger than 4\n", __func__); + return -EOPNOTSUPP; + } + + for (i = 0; i < IMX_MU_CHANS; i++) { + struct imx_mu_con_priv *cp = &priv->con_priv[i]; + + cp->idx = i % 4; + cp->type = i >> 2; + cp->chan = &priv->mbox_chans[i]; + priv->mbox_chans[i].con_priv = cp; + snprintf(cp->irq_desc, sizeof(cp->irq_desc), + "%s[%i-%u]", dev_name(priv->dev), cp->type, cp->idx); + } + + priv->mbox.num_chans = IMX_MU_CHANS; + priv->mbox.of_xlate = imx_mu_xlate; + + if (priv->side_b) + return 0; + + /* Set default MU configuration */ + for (i = 0; i < IMX_MU_xCR_MAX; i++) + imx_mu_write(priv, 0, priv->dcfg->xCR[i]); + + /* Clear any pending GIP */ + val = imx_mu_read(priv, priv->dcfg->xSR[IMX_MU_GSR]); + imx_mu_write(priv, val, priv->dcfg->xSR[IMX_MU_GSR]); + + /* Clear any pending RSR */ + for (i = 0; i < priv->num_rr; i++) + imx_mu_read(priv, priv->dcfg->xRR + i * 4); + + return 0; +} + +static int imx_mu_init_specific(struct imx_mu_priv *priv) +{ + unsigned int i; + int num_chans = priv->dcfg->type & IMX_MU_V2_S4 ? IMX_MU_S4_CHANS : IMX_MU_SCU_CHANS; + + for (i = 0; i < num_chans; i++) { + struct imx_mu_con_priv *cp = &priv->con_priv[i]; + + cp->idx = i < 2 ? 0 : i - 2; + cp->type = i < 2 ? i : IMX_MU_TYPE_RXDB; + cp->chan = &priv->mbox_chans[i]; + priv->mbox_chans[i].con_priv = cp; + snprintf(cp->irq_desc, sizeof(cp->irq_desc), + "%s[%i-%u]", dev_name(priv->dev), cp->type, cp->idx); + } + + priv->mbox.num_chans = num_chans; + priv->mbox.of_xlate = imx_mu_specific_xlate; + + /* Set default MU configuration */ + for (i = 0; i < IMX_MU_xCR_MAX; i++) + imx_mu_write(priv, 0, priv->dcfg->xCR[i]); + + return 0; +} + +static int imx_mu_init_seco(struct imx_mu_priv *priv) +{ + int ret; + + ret = imx_mu_init_generic(priv); + if (ret) + return ret; + priv->mbox.of_xlate = imx_mu_seco_xlate; + + return 0; +} + +static int imx_mu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct imx_mu_priv *priv; + const struct imx_mu_dcfg *dcfg; + int i, ret; + u32 size; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + dcfg = of_device_get_match_data(dev); + if (!dcfg) + return -EINVAL; + priv->dcfg = dcfg; + if (priv->dcfg->type & IMX_MU_V2_IRQ) { + priv->irq[IMX_MU_TYPE_TX] = platform_get_irq_byname(pdev, "tx"); + if (priv->irq[IMX_MU_TYPE_TX] < 0) + return priv->irq[IMX_MU_TYPE_TX]; + priv->irq[IMX_MU_TYPE_RX] = platform_get_irq_byname(pdev, "rx"); + if (priv->irq[IMX_MU_TYPE_RX] < 0) + return priv->irq[IMX_MU_TYPE_RX]; + } else { + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + + for (i = 0; i < IMX_MU_CHANS; i++) + priv->irq[i] = ret; + } + + if (priv->dcfg->type & IMX_MU_V2_S4) + size = sizeof(struct imx_s4_rpc_msg_max); + else + size = sizeof(struct imx_sc_rpc_msg_max); + + priv->msg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!priv->msg) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + if (PTR_ERR(priv->clk) != -ENOENT) + return PTR_ERR(priv->clk); + + priv->clk = NULL; + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + return ret; + } + + imx_mu_get_tr_rr(priv); + + priv->side_b = of_property_read_bool(np, "fsl,mu-side-b"); + + ret = priv->dcfg->init(priv); + if (ret) { + dev_err(dev, "Failed to init MU\n"); + goto disable_clk; + } + + raw_spin_lock_init(&priv->xcr_lock); + + priv->mbox.dev = dev; + priv->mbox.ops = &imx_mu_ops; + priv->mbox.chans = priv->mbox_chans; + priv->mbox.txdone_irq = true; + + platform_set_drvdata(pdev, priv); + + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + goto disable_clk; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto disable_clk; + + ret = pm_runtime_put_sync(dev); + if (ret < 0) + goto disable_clk; + + clk_disable_unprepare(priv->clk); + + ret = devm_mbox_controller_register(dev, &priv->mbox); + if (ret) + goto err_out; + + devm_of_platform_populate(dev); + + return 0; + +disable_clk: + clk_disable_unprepare(priv->clk); +err_out: + return ret; +} + +static void imx_mu_remove(struct platform_device *pdev) +{ +} + +static const struct imx_mu_dcfg imx_mu_cfg_imx6sx = { + .tx = imx_mu_generic_tx, + .rx = imx_mu_generic_rx, + .rxdb = imx_mu_generic_rxdb, + .init = imx_mu_init_generic, + .xTR = 0x0, + .xRR = 0x10, + .xSR = {0x20, 0x20, 0x20, 0x20}, + .xCR = {0x24, 0x24, 0x24, 0x24, 0x24}, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx7ulp = { + .tx = imx_mu_generic_tx, + .rx = imx_mu_generic_rx, + .rxdb = imx_mu_generic_rxdb, + .init = imx_mu_init_generic, + .xTR = 0x20, + .xRR = 0x40, + .xSR = {0x60, 0x60, 0x60, 0x60}, + .xCR = {0x64, 0x64, 0x64, 0x64, 0x64}, + .skip_suspend_flag = true, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx8ulp = { + .tx = imx_mu_generic_tx, + .rx = imx_mu_generic_rx, + .rxdb = imx_mu_generic_rxdb, + .init = imx_mu_init_generic, + .type = IMX_MU_V2, + .xTR = 0x200, + .xRR = 0x280, + .xSR = {0xC, 0x118, 0x124, 0x12C}, + .xCR = {0x8, 0x110, 0x114, 0x120, 0x128}, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx8ulp_s4 = { + .tx = imx_mu_specific_tx, + .rx = imx_mu_specific_rx, + .init = imx_mu_init_specific, + .type = IMX_MU_V2 | IMX_MU_V2_S4, + .xTR = 0x200, + .xRR = 0x280, + .xSR = {0xC, 0x118, 0x124, 0x12C}, + .xCR = {0x8, 0x110, 0x114, 0x120, 0x128}, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx93_s4 = { + .tx = imx_mu_specific_tx, + .rx = imx_mu_specific_rx, + .init = imx_mu_init_specific, + .type = IMX_MU_V2 | IMX_MU_V2_S4 | IMX_MU_V2_IRQ, + .xTR = 0x200, + .xRR = 0x280, + .xSR = {0xC, 0x118, 0x124, 0x12C}, + .xCR = {0x8, 0x110, 0x114, 0x120, 0x128}, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx8_scu = { + .tx = imx_mu_specific_tx, + .rx = imx_mu_specific_rx, + .init = imx_mu_init_specific, + .rxdb = imx_mu_generic_rxdb, + .xTR = 0x0, + .xRR = 0x10, + .xSR = {0x20, 0x20, 0x20, 0x20}, + .xCR = {0x24, 0x24, 0x24, 0x24, 0x24}, +}; + +static const struct imx_mu_dcfg imx_mu_cfg_imx8_seco = { + .tx = imx_mu_seco_tx, + .rx = imx_mu_generic_rx, + .rxdb = imx_mu_seco_rxdb, + .init = imx_mu_init_seco, + .xTR = 0x0, + .xRR = 0x10, + .xSR = {0x20, 0x20, 0x20, 0x20}, + .xCR = {0x24, 0x24, 0x24, 0x24, 0x24}, +}; + +static const struct of_device_id imx_mu_dt_ids[] = { + { .compatible = "fsl,imx7ulp-mu", .data = &imx_mu_cfg_imx7ulp }, + { .compatible = "fsl,imx6sx-mu", .data = &imx_mu_cfg_imx6sx }, + { .compatible = "fsl,imx8ulp-mu", .data = &imx_mu_cfg_imx8ulp }, + { .compatible = "fsl,imx8ulp-mu-s4", .data = &imx_mu_cfg_imx8ulp_s4 }, + { .compatible = "fsl,imx93-mu-s4", .data = &imx_mu_cfg_imx93_s4 }, + { .compatible = "fsl,imx95-mu", .data = &imx_mu_cfg_imx8ulp }, + { .compatible = "fsl,imx95-mu-ele", .data = &imx_mu_cfg_imx8ulp_s4 }, + { .compatible = "fsl,imx95-mu-v2x", .data = &imx_mu_cfg_imx8ulp_s4 }, + { .compatible = "fsl,imx8-mu-scu", .data = &imx_mu_cfg_imx8_scu }, + { .compatible = "fsl,imx8-mu-seco", .data = &imx_mu_cfg_imx8_seco }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_mu_dt_ids); + +static int __maybe_unused imx_mu_suspend_noirq(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + int i; + + if (!priv->clk) { + for (i = 0; i < IMX_MU_xCR_MAX; i++) + priv->xcr[i] = imx_mu_read(priv, priv->dcfg->xCR[i]); + } + + if (!priv->dcfg->skip_suspend_flag) + priv->suspend = true; + + return 0; +} + +static int __maybe_unused imx_mu_resume_noirq(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + int i; + + /* + * ONLY restore MU when context lost, the TIE could + * be set during noirq resume as there is MU data + * communication going on, and restore the saved + * value will overwrite the TIE and cause MU data + * send failed, may lead to system freeze. This issue + * is observed by testing freeze mode suspend. + */ + if (!priv->clk && !imx_mu_read(priv, priv->dcfg->xCR[0])) { + for (i = 0; i < IMX_MU_xCR_MAX; i++) + imx_mu_write(priv, priv->xcr[i], priv->dcfg->xCR[i]); + } + + if (!priv->dcfg->skip_suspend_flag) + priv->suspend = false; + + return 0; +} + +static int __maybe_unused imx_mu_runtime_suspend(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int __maybe_unused imx_mu_runtime_resume(struct device *dev) +{ + struct imx_mu_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + dev_err(dev, "failed to enable clock\n"); + + return ret; +} + +static const struct dev_pm_ops imx_mu_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_mu_suspend_noirq, + imx_mu_resume_noirq) + SET_RUNTIME_PM_OPS(imx_mu_runtime_suspend, + imx_mu_runtime_resume, NULL) +}; + +static struct platform_driver imx_mu_driver = { + .probe = imx_mu_probe, + .remove = imx_mu_remove, + .driver = { + .name = "imx_mu", + .of_match_table = imx_mu_dt_ids, + .pm = &imx_mu_pm_ops, + }, +}; +module_platform_driver(imx_mu_driver); + +MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>"); +MODULE_DESCRIPTION("Message Unit driver for i.MX"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/mailbox-altera.c b/drivers/mailbox/mailbox-altera.c new file mode 100644 index 000000000..17278c257 --- /dev/null +++ b/drivers/mailbox/mailbox-altera.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright Altera Corporation (C) 2013-2014. All rights reserved + */ + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define DRIVER_NAME "altera-mailbox" + +#define MAILBOX_CMD_REG 0x00 +#define MAILBOX_PTR_REG 0x04 +#define MAILBOX_STS_REG 0x08 +#define MAILBOX_INTMASK_REG 0x0C + +#define INT_PENDING_MSK 0x1 +#define INT_SPACE_MSK 0x2 + +#define STS_PENDING_MSK 0x1 +#define STS_FULL_MSK 0x2 +#define STS_FULL_OFT 0x1 + +#define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) +#define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) + +enum altera_mbox_msg { + MBOX_CMD = 0, + MBOX_PTR, +}; + +#define MBOX_POLLING_MS 5 /* polling interval 5ms */ + +struct altera_mbox { + bool is_sender; /* 1-sender, 0-receiver */ + bool intr_mode; + int irq; + void __iomem *mbox_base; + struct device *dev; + struct mbox_controller controller; + + /* If the controller supports only RX polling mode */ + struct timer_list rxpoll_timer; + struct mbox_chan *chan; +}; + +static struct altera_mbox *mbox_chan_to_altera_mbox(struct mbox_chan *chan) +{ + if (!chan || !chan->con_priv) + return NULL; + + return (struct altera_mbox *)chan->con_priv; +} + +static inline int altera_mbox_full(struct altera_mbox *mbox) +{ + u32 status; + + status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_FULL(status); +} + +static inline int altera_mbox_pending(struct altera_mbox *mbox) +{ + u32 status; + + status = readl_relaxed(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_PENDING(status); +} + +static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + + mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_PENDING_MSK; + else + mask &= ~INT_PENDING_MSK; + writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + + mask = readl_relaxed(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_SPACE_MSK; + else + mask &= ~INT_SPACE_MSK; + writel_relaxed(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static bool altera_mbox_is_sender(struct altera_mbox *mbox) +{ + u32 reg; + /* Write a magic number to PTR register and read back this register. + * This register is read-write if it is a sender. + */ + #define MBOX_MAGIC 0xA5A5AA55 + writel_relaxed(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); + reg = readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); + if (reg == MBOX_MAGIC) { + /* Clear to 0 */ + writel_relaxed(0, mbox->mbox_base + MAILBOX_PTR_REG); + return true; + } + return false; +} + +static void altera_mbox_rx_data(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + u32 data[2]; + + if (altera_mbox_pending(mbox)) { + data[MBOX_PTR] = + readl_relaxed(mbox->mbox_base + MAILBOX_PTR_REG); + data[MBOX_CMD] = + readl_relaxed(mbox->mbox_base + MAILBOX_CMD_REG); + mbox_chan_received_data(chan, (void *)data); + } +} + +static void altera_mbox_poll_rx(struct timer_list *t) +{ + struct altera_mbox *mbox = timer_container_of(mbox, t, rxpoll_timer); + + altera_mbox_rx_data(mbox->chan); + + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); +} + +static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = (struct mbox_chan *)p; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + altera_mbox_tx_intmask(mbox, false); + mbox_chan_txdone(chan, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = (struct mbox_chan *)p; + + altera_mbox_rx_data(chan); + return IRQ_HANDLED; +} + +static int altera_mbox_startup_sender(struct mbox_chan *chan) +{ + int ret; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, + DRIVER_NAME, chan); + if (unlikely(ret)) { + dev_err(mbox->dev, + "failed to register mailbox interrupt:%d\n", + ret); + return ret; + } + } + + return 0; +} + +static int altera_mbox_startup_receiver(struct mbox_chan *chan) +{ + int ret; + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, + DRIVER_NAME, chan); + if (unlikely(ret)) { + mbox->intr_mode = false; + goto polling; /* use polling if failed */ + } + + altera_mbox_rx_intmask(mbox, true); + return 0; + } + +polling: + /* Setup polling timer */ + mbox->chan = chan; + timer_setup(&mbox->rxpoll_timer, altera_mbox_poll_rx, 0); + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); + + return 0; +} + +static int altera_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + u32 *udata = (u32 *)data; + + if (!mbox || !data) + return -EINVAL; + if (!mbox->is_sender) { + dev_warn(mbox->dev, + "failed to send. This is receiver mailbox.\n"); + return -EINVAL; + } + + if (altera_mbox_full(mbox)) + return -EBUSY; + + /* Enable interrupt before send */ + if (mbox->intr_mode) + altera_mbox_tx_intmask(mbox, true); + + /* Pointer register must write before command register */ + writel_relaxed(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); + writel_relaxed(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); + + return 0; +} + +static bool altera_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + /* Return false if mailbox is full */ + return altera_mbox_full(mbox) ? false : true; +} + +static bool altera_mbox_peek_data(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + return altera_mbox_pending(mbox) ? true : false; +} + +static int altera_mbox_startup(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + int ret = 0; + + if (!mbox) + return -EINVAL; + + if (mbox->is_sender) + ret = altera_mbox_startup_sender(chan); + else + ret = altera_mbox_startup_receiver(chan); + + return ret; +} + +static void altera_mbox_shutdown(struct mbox_chan *chan) +{ + struct altera_mbox *mbox = mbox_chan_to_altera_mbox(chan); + + if (mbox->intr_mode) { + /* Unmask all interrupt masks */ + writel_relaxed(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); + free_irq(mbox->irq, chan); + } else if (!mbox->is_sender) { + timer_delete_sync(&mbox->rxpoll_timer); + } +} + +static const struct mbox_chan_ops altera_mbox_ops = { + .send_data = altera_mbox_send_data, + .startup = altera_mbox_startup, + .shutdown = altera_mbox_shutdown, + .last_tx_done = altera_mbox_last_tx_done, + .peek_data = altera_mbox_peek_data, +}; + +static int altera_mbox_probe(struct platform_device *pdev) +{ + struct altera_mbox *mbox; + struct mbox_chan *chans; + int ret; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), + GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + /* Allocated one channel */ + chans = devm_kzalloc(&pdev->dev, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mbox->mbox_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->mbox_base)) + return PTR_ERR(mbox->mbox_base); + + /* Check is it a sender or receiver? */ + mbox->is_sender = altera_mbox_is_sender(mbox); + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq >= 0) + mbox->intr_mode = true; + + mbox->dev = &pdev->dev; + + /* Hardware supports only one channel. */ + chans[0].con_priv = mbox; + mbox->controller.dev = mbox->dev; + mbox->controller.num_chans = 1; + mbox->controller.chans = chans; + mbox->controller.ops = &altera_mbox_ops; + + if (mbox->is_sender) { + if (mbox->intr_mode) { + mbox->controller.txdone_irq = true; + } else { + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = MBOX_POLLING_MS; + } + } + + ret = devm_mbox_controller_register(&pdev->dev, &mbox->controller); + if (ret) { + dev_err(&pdev->dev, "Register mailbox failed\n"); + goto err; + } + + platform_set_drvdata(pdev, mbox); +err: + return ret; +} + +static const struct of_device_id altera_mbox_match[] = { + { .compatible = "altr,mailbox-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mbox_match); + +static struct platform_driver altera_mbox_driver = { + .probe = altera_mbox_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = altera_mbox_match, + }, +}; + +module_platform_driver(altera_mbox_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera mailbox specific functions"); +MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); +MODULE_ALIAS("platform:altera-mailbox"); diff --git a/drivers/mailbox/mailbox-mchp-ipc-sbi.c b/drivers/mailbox/mailbox-mchp-ipc-sbi.c new file mode 100644 index 000000000..f081f8a9b --- /dev/null +++ b/drivers/mailbox/mailbox-mchp-ipc-sbi.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip Inter-Processor communication (IPC) driver + * + * Copyright (c) 2021 - 2024 Microchip Technology Inc. All rights reserved. + * + * Author: Valentina Fernandez <valentina.fernandezalanis@microchip.com> + * + */ + +#include <linux/io.h> +#include <linux/err.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/mailbox/mchp-ipc.h> +#include <asm/sbi.h> +#include <asm/vendorid_list.h> + +#define IRQ_STATUS_BITS 12 +#define NUM_CHANS_PER_CLUSTER 5 +#define IPC_DMA_BIT_MASK 32 +#define SBI_EXT_MICROCHIP_TECHNOLOGY (SBI_EXT_VENDOR_START | \ + MICROCHIP_VENDOR_ID) + +enum { + SBI_EXT_IPC_PROBE = 0x100, + SBI_EXT_IPC_CH_INIT, + SBI_EXT_IPC_SEND, + SBI_EXT_IPC_RECEIVE, + SBI_EXT_IPC_STATUS, +}; + +enum ipc_hw { + MIV_IHC, +}; + +/** + * struct mchp_ipc_mbox_info - IPC probe message format + * + * @hw_type: IPC implementation available in the hardware + * @num_channels: number of IPC channels available in the hardware + * + * Used to retrieve information on the IPC implementation + * using the SBI_EXT_IPC_PROBE SBI function id. + */ +struct mchp_ipc_mbox_info { + enum ipc_hw hw_type; + u8 num_channels; +}; + +/** + * struct mchp_ipc_init - IPC channel init message format + * + * @max_msg_size: maxmimum message size in bytes of a given channel + * + * struct used by the SBI_EXT_IPC_CH_INIT SBI function id to get + * the max message size in bytes of the initialized channel. + */ +struct mchp_ipc_init { + u16 max_msg_size; +}; + +/** + * struct mchp_ipc_status - IPC status message format + * + * @status: interrupt status for all channels associated to a cluster + * @cluster: specifies the cluster instance that originated an irq + * + * struct used by the SBI_EXT_IPC_STATUS SBI function id to get + * the message present and message clear interrupt status for all the + * channels associated to a cluster. + */ +struct mchp_ipc_status { + u32 status; + u8 cluster; +}; + +/** + * struct mchp_ipc_sbi_msg - IPC SBI payload message + * + * @buf_addr: physical address where the received data should be copied to + * @size: maximum size(in bytes) that can be stored in the buffer pointed to by `buf` + * @irq_type: mask representing the irq types that triggered an irq + * + * struct used by the SBI_EXT_IPC_SEND/SBI_EXT_IPC_RECEIVE SBI function + * ids to send/receive a message from an associated processor using + * the IPC. + */ +struct mchp_ipc_sbi_msg { + u64 buf_addr; + u16 size; + u8 irq_type; +}; + +struct mchp_ipc_cluster_cfg { + void *buf_base; + phys_addr_t buf_base_addr; + int irq; +}; + +struct mchp_ipc_sbi_mbox { + struct device *dev; + struct mbox_chan *chans; + struct mchp_ipc_cluster_cfg *cluster_cfg; + void *buf_base; + unsigned long buf_base_addr; + struct mbox_controller controller; + enum ipc_hw hw_type; +}; + +static int mchp_ipc_sbi_chan_send(u32 command, u32 channel, unsigned long address) +{ + struct sbiret ret; + + ret = sbi_ecall(SBI_EXT_MICROCHIP_TECHNOLOGY, command, channel, + address, 0, 0, 0, 0); + + if (ret.error) + return sbi_err_map_linux_errno(ret.error); + else + return ret.value; +} + +static int mchp_ipc_sbi_send(u32 command, unsigned long address) +{ + struct sbiret ret; + + ret = sbi_ecall(SBI_EXT_MICROCHIP_TECHNOLOGY, command, address, + 0, 0, 0, 0, 0); + + if (ret.error) + return sbi_err_map_linux_errno(ret.error); + else + return ret.value; +} + +static struct mchp_ipc_sbi_mbox *to_mchp_ipc_mbox(struct mbox_controller *mbox) +{ + return container_of(mbox, struct mchp_ipc_sbi_mbox, controller); +} + +static inline void mchp_ipc_prepare_receive_req(struct mbox_chan *chan) +{ + struct mchp_ipc_sbi_chan *chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + struct mchp_ipc_sbi_msg request; + + request.buf_addr = chan_info->msg_buf_rx_addr; + request.size = chan_info->max_msg_size; + memcpy(chan_info->buf_base_rx, &request, sizeof(struct mchp_ipc_sbi_msg)); +} + +static inline void mchp_ipc_process_received_data(struct mbox_chan *chan, + struct mchp_ipc_msg *ipc_msg) +{ + struct mchp_ipc_sbi_chan *chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + struct mchp_ipc_sbi_msg sbi_msg; + + memcpy(&sbi_msg, chan_info->buf_base_rx, sizeof(struct mchp_ipc_sbi_msg)); + ipc_msg->buf = (u32 *)chan_info->msg_buf_rx; + ipc_msg->size = sbi_msg.size; +} + +static irqreturn_t mchp_ipc_cluster_aggr_isr(int irq, void *data) +{ + struct mbox_chan *chan; + struct mchp_ipc_sbi_chan *chan_info; + struct mchp_ipc_sbi_mbox *ipc = (struct mchp_ipc_sbi_mbox *)data; + struct mchp_ipc_msg ipc_msg; + struct mchp_ipc_status status_msg; + int ret; + u32 i, chan_index, chan_id; + bool found = false; + + /* Find out the hart that originated the irq */ + for_each_online_cpu(i) { + if (irq == ipc->cluster_cfg[i].irq) { + found = true; + break; + } + } + + if (unlikely(!found)) + return IRQ_NONE; + + status_msg.cluster = cpuid_to_hartid_map(i); + memcpy(ipc->cluster_cfg[i].buf_base, &status_msg, sizeof(struct mchp_ipc_status)); + + ret = mchp_ipc_sbi_send(SBI_EXT_IPC_STATUS, ipc->cluster_cfg[i].buf_base_addr); + if (ret < 0) { + dev_err_ratelimited(ipc->dev, "could not get IHC irq status ret=%d\n", ret); + return IRQ_HANDLED; + } + + memcpy(&status_msg, ipc->cluster_cfg[i].buf_base, sizeof(struct mchp_ipc_status)); + + /* + * Iterate over each bit set in the IHC interrupt status register (IRQ_STATUS) to identify + * the channel(s) that have a message to be processed/acknowledged. + * The bits are organized in alternating format, where each pair of bits represents + * the status of the message present and message clear interrupts for each cluster/hart + * (from hart 0 to hart 5). Each cluster can have up to 5 fixed channels associated. + */ + + for_each_set_bit(i, (unsigned long *)&status_msg.status, IRQ_STATUS_BITS) { + /* Find out the destination hart that triggered the interrupt */ + chan_index = i / 2; + + /* + * The IP has no loopback channels, so we need to decrement the index when + * the target hart has a greater index than our own + */ + if (chan_index >= status_msg.cluster) + chan_index--; + + /* + * Calculate the channel id given the hart and channel index. Channel IDs + * are unique across all clusters of an IPC, and iterate contiguously + * across all clusters. + */ + chan_id = status_msg.cluster * (NUM_CHANS_PER_CLUSTER + chan_index); + + chan = &ipc->chans[chan_id]; + chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + + if (i % 2 == 0) { + mchp_ipc_prepare_receive_req(chan); + ret = mchp_ipc_sbi_chan_send(SBI_EXT_IPC_RECEIVE, chan_id, + chan_info->buf_base_rx_addr); + if (ret < 0) + continue; + + mchp_ipc_process_received_data(chan, &ipc_msg); + mbox_chan_received_data(&ipc->chans[chan_id], (void *)&ipc_msg); + + } else { + ret = mchp_ipc_sbi_chan_send(SBI_EXT_IPC_RECEIVE, chan_id, + chan_info->buf_base_rx_addr); + mbox_chan_txdone(&ipc->chans[chan_id], ret); + } + } + return IRQ_HANDLED; +} + +static int mchp_ipc_send_data(struct mbox_chan *chan, void *data) +{ + struct mchp_ipc_sbi_chan *chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + const struct mchp_ipc_msg *msg = data; + struct mchp_ipc_sbi_msg sbi_payload; + + memcpy(chan_info->msg_buf_tx, msg->buf, msg->size); + sbi_payload.buf_addr = chan_info->msg_buf_tx_addr; + sbi_payload.size = msg->size; + memcpy(chan_info->buf_base_tx, &sbi_payload, sizeof(sbi_payload)); + + return mchp_ipc_sbi_chan_send(SBI_EXT_IPC_SEND, chan_info->id, chan_info->buf_base_tx_addr); +} + +static int mchp_ipc_startup(struct mbox_chan *chan) +{ + struct mchp_ipc_sbi_chan *chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + struct mchp_ipc_sbi_mbox *ipc = to_mchp_ipc_mbox(chan->mbox); + struct mchp_ipc_init ch_init_msg; + int ret; + + /* + * The TX base buffer is used to transmit two types of messages: + * - struct mchp_ipc_init to initialize the channel + * - struct mchp_ipc_sbi_msg to transmit user data/payload + * Ensure the TX buffer size is large enough to accommodate either message type. + */ + size_t max_size = max(sizeof(struct mchp_ipc_init), sizeof(struct mchp_ipc_sbi_msg)); + + chan_info->buf_base_tx = kmalloc(max_size, GFP_KERNEL); + if (!chan_info->buf_base_tx) { + ret = -ENOMEM; + goto fail; + } + + chan_info->buf_base_tx_addr = __pa(chan_info->buf_base_tx); + + chan_info->buf_base_rx = kmalloc(max_size, GFP_KERNEL); + if (!chan_info->buf_base_rx) { + ret = -ENOMEM; + goto fail_free_buf_base_tx; + } + + chan_info->buf_base_rx_addr = __pa(chan_info->buf_base_rx); + + ret = mchp_ipc_sbi_chan_send(SBI_EXT_IPC_CH_INIT, chan_info->id, + chan_info->buf_base_tx_addr); + if (ret < 0) { + dev_err(ipc->dev, "channel %u init failed\n", chan_info->id); + goto fail_free_buf_base_rx; + } + + memcpy(&ch_init_msg, chan_info->buf_base_tx, sizeof(struct mchp_ipc_init)); + chan_info->max_msg_size = ch_init_msg.max_msg_size; + + chan_info->msg_buf_tx = kmalloc(chan_info->max_msg_size, GFP_KERNEL); + if (!chan_info->msg_buf_tx) { + ret = -ENOMEM; + goto fail_free_buf_base_rx; + } + + chan_info->msg_buf_tx_addr = __pa(chan_info->msg_buf_tx); + + chan_info->msg_buf_rx = kmalloc(chan_info->max_msg_size, GFP_KERNEL); + if (!chan_info->msg_buf_rx) { + ret = -ENOMEM; + goto fail_free_buf_msg_tx; + } + + chan_info->msg_buf_rx_addr = __pa(chan_info->msg_buf_rx); + + switch (ipc->hw_type) { + case MIV_IHC: + return 0; + default: + goto fail_free_buf_msg_rx; + } + +fail_free_buf_msg_rx: + kfree(chan_info->msg_buf_rx); +fail_free_buf_msg_tx: + kfree(chan_info->msg_buf_tx); +fail_free_buf_base_rx: + kfree(chan_info->buf_base_rx); +fail_free_buf_base_tx: + kfree(chan_info->buf_base_tx); +fail: + return ret; +} + +static void mchp_ipc_shutdown(struct mbox_chan *chan) +{ + struct mchp_ipc_sbi_chan *chan_info = (struct mchp_ipc_sbi_chan *)chan->con_priv; + + kfree(chan_info->buf_base_tx); + kfree(chan_info->buf_base_rx); + kfree(chan_info->msg_buf_tx); + kfree(chan_info->msg_buf_rx); +} + +static const struct mbox_chan_ops mchp_ipc_ops = { + .startup = mchp_ipc_startup, + .send_data = mchp_ipc_send_data, + .shutdown = mchp_ipc_shutdown, +}; + +static struct mbox_chan *mchp_ipc_mbox_xlate(struct mbox_controller *controller, + const struct of_phandle_args *spec) +{ + struct mchp_ipc_sbi_mbox *ipc = to_mchp_ipc_mbox(controller); + unsigned int chan_id = spec->args[0]; + + if (chan_id >= ipc->controller.num_chans) { + dev_err(ipc->dev, "invalid channel id %d\n", chan_id); + return ERR_PTR(-EINVAL); + } + + return &ipc->chans[chan_id]; +} + +static int mchp_ipc_get_cluster_aggr_irq(struct mchp_ipc_sbi_mbox *ipc) +{ + struct platform_device *pdev = to_platform_device(ipc->dev); + char *irq_name; + int cpuid, ret; + unsigned long hartid; + bool irq_found = false; + + for_each_online_cpu(cpuid) { + hartid = cpuid_to_hartid_map(cpuid); + irq_name = devm_kasprintf(ipc->dev, GFP_KERNEL, "hart-%lu", hartid); + if (!irq_name) + return -ENOMEM; + ret = platform_get_irq_byname_optional(pdev, irq_name); + if (ret <= 0) + continue; + + ipc->cluster_cfg[cpuid].irq = ret; + ret = devm_request_irq(ipc->dev, ipc->cluster_cfg[cpuid].irq, + mchp_ipc_cluster_aggr_isr, IRQF_SHARED, + "miv-ihc-irq", ipc); + if (ret) + return ret; + + ipc->cluster_cfg[cpuid].buf_base = devm_kmalloc(ipc->dev, + sizeof(struct mchp_ipc_status), + GFP_KERNEL); + + if (!ipc->cluster_cfg[cpuid].buf_base) + return -ENOMEM; + + ipc->cluster_cfg[cpuid].buf_base_addr = __pa(ipc->cluster_cfg[cpuid].buf_base); + + irq_found = true; + } + + return irq_found; +} + +static int mchp_ipc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mchp_ipc_mbox_info ipc_info; + struct mchp_ipc_sbi_mbox *ipc; + struct mchp_ipc_sbi_chan *priv; + bool irq_avail = false; + int ret; + u32 chan_id; + + ret = sbi_probe_extension(SBI_EXT_MICROCHIP_TECHNOLOGY); + if (ret <= 0) + return dev_err_probe(dev, -ENODEV, "Microchip SBI extension not detected\n"); + + ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + + platform_set_drvdata(pdev, ipc); + + ipc->buf_base = devm_kmalloc(dev, sizeof(struct mchp_ipc_mbox_info), GFP_KERNEL); + if (!ipc->buf_base) + return -ENOMEM; + + ipc->buf_base_addr = __pa(ipc->buf_base); + + ret = mchp_ipc_sbi_send(SBI_EXT_IPC_PROBE, ipc->buf_base_addr); + if (ret < 0) + return dev_err_probe(dev, ret, "could not probe IPC SBI service\n"); + + memcpy(&ipc_info, ipc->buf_base, sizeof(struct mchp_ipc_mbox_info)); + ipc->controller.num_chans = ipc_info.num_channels; + ipc->hw_type = ipc_info.hw_type; + + ipc->chans = devm_kcalloc(dev, ipc->controller.num_chans, sizeof(*ipc->chans), GFP_KERNEL); + if (!ipc->chans) + return -ENOMEM; + + ipc->dev = dev; + ipc->controller.txdone_irq = true; + ipc->controller.dev = ipc->dev; + ipc->controller.ops = &mchp_ipc_ops; + ipc->controller.chans = ipc->chans; + ipc->controller.of_xlate = mchp_ipc_mbox_xlate; + + for (chan_id = 0; chan_id < ipc->controller.num_chans; chan_id++) { + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ipc->chans[chan_id].con_priv = priv; + priv->id = chan_id; + } + + if (ipc->hw_type == MIV_IHC) { + ipc->cluster_cfg = devm_kcalloc(dev, num_online_cpus(), + sizeof(struct mchp_ipc_cluster_cfg), + GFP_KERNEL); + if (!ipc->cluster_cfg) + return -ENOMEM; + + if (mchp_ipc_get_cluster_aggr_irq(ipc)) + irq_avail = true; + } + + if (!irq_avail) + return dev_err_probe(dev, -ENODEV, "missing interrupt property\n"); + + ret = devm_mbox_controller_register(dev, &ipc->controller); + if (ret) + return dev_err_probe(dev, ret, + "Inter-Processor communication (IPC) registration failed\n"); + + return 0; +} + +static const struct of_device_id mchp_ipc_of_match[] = { + {.compatible = "microchip,sbi-ipc", }, + {} +}; +MODULE_DEVICE_TABLE(of, mchp_ipc_of_match); + +static struct platform_driver mchp_ipc_driver = { + .driver = { + .name = "microchip_ipc", + .of_match_table = mchp_ipc_of_match, + }, + .probe = mchp_ipc_probe, +}; + +module_platform_driver(mchp_ipc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Valentina Fernandez <valentina.fernandezalanis@microchip.com>"); +MODULE_DESCRIPTION("Microchip Inter-Processor Communication (IPC) driver"); diff --git a/drivers/mailbox/mailbox-mpfs.c b/drivers/mailbox/mailbox-mpfs.c new file mode 100644 index 000000000..704a259bb --- /dev/null +++ b/drivers/mailbox/mailbox-mpfs.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip PolarFire SoC (MPFS) system controller/mailbox controller driver + * + * Copyright (c) 2020-2022 Microchip Corporation. All rights reserved. + * + * Author: Conor Dooley <conor.dooley@microchip.com> + * + */ + +#include <linux/io.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/platform_device.h> +#include <linux/mailbox_controller.h> +#include <soc/microchip/mpfs.h> + +#define MESSAGE_INT_OFFSET 0x18cu +#define SERVICES_CR_OFFSET 0x50u +#define SERVICES_SR_OFFSET 0x54u +#define MAILBOX_REG_OFFSET 0x800u +#define MSS_SYS_MAILBOX_DATA_OFFSET 0u +#define SCB_MASK_WIDTH 16u + +/* SCBCTRL service control register */ + +#define SCB_CTRL_REQ (0) +#define SCB_CTRL_REQ_MASK BIT(SCB_CTRL_REQ) + +#define SCB_CTRL_BUSY (1) +#define SCB_CTRL_BUSY_MASK BIT(SCB_CTRL_BUSY) + +#define SCB_CTRL_ABORT (2) +#define SCB_CTRL_ABORT_MASK BIT(SCB_CTRL_ABORT) + +#define SCB_CTRL_NOTIFY (3) +#define SCB_CTRL_NOTIFY_MASK BIT(SCB_CTRL_NOTIFY) + +#define SCB_CTRL_POS (16) +#define SCB_CTRL_MASK GENMASK(SCB_CTRL_POS + SCB_MASK_WIDTH - 1, SCB_CTRL_POS) + +/* SCBCTRL service status register */ + +#define SCB_STATUS_REQ (0) +#define SCB_STATUS_REQ_MASK BIT(SCB_STATUS_REQ) + +#define SCB_STATUS_BUSY (1) +#define SCB_STATUS_BUSY_MASK BIT(SCB_STATUS_BUSY) + +#define SCB_STATUS_ABORT (2) +#define SCB_STATUS_ABORT_MASK BIT(SCB_STATUS_ABORT) + +#define SCB_STATUS_NOTIFY (3) +#define SCB_STATUS_NOTIFY_MASK BIT(SCB_STATUS_NOTIFY) + +#define SCB_STATUS_POS (16) +#define SCB_STATUS_MASK GENMASK(SCB_STATUS_POS + SCB_MASK_WIDTH - 1, SCB_STATUS_POS) + +struct mpfs_mbox { + struct mbox_controller controller; + struct device *dev; + int irq; + void __iomem *ctrl_base; + void __iomem *mbox_base; + void __iomem *int_reg; + struct mbox_chan chans[1]; + struct mpfs_mss_response *response; + struct regmap *sysreg_scb, *control_scb; + u16 resp_offset; +}; + +static bool mpfs_mbox_busy(struct mpfs_mbox *mbox) +{ + u32 status; + + if (mbox->control_scb) + regmap_read(mbox->control_scb, SERVICES_SR_OFFSET, &status); + else + status = readl_relaxed(mbox->ctrl_base + SERVICES_SR_OFFSET); + + return status & SCB_STATUS_BUSY_MASK; +} + +static bool mpfs_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + struct mpfs_mss_response *response = mbox->response; + u32 val; + + if (mpfs_mbox_busy(mbox)) + return false; + + /* + * The service status is stored in bits 31:16 of the SERVICES_SR + * register & is only valid when the system controller is not busy. + * Failed services are intended to generated interrupts, but in reality + * this does not happen, so the status must be checked here. + */ + if (mbox->control_scb) + regmap_read(mbox->control_scb, SERVICES_SR_OFFSET, &val); + else + val = readl_relaxed(mbox->ctrl_base + SERVICES_SR_OFFSET); + + response->resp_status = (val & SCB_STATUS_MASK) >> SCB_STATUS_POS; + + return true; +} + +static int mpfs_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + struct mpfs_mss_msg *msg = data; + u32 tx_trigger; + u16 opt_sel; + u32 val = 0u; + + mbox->response = msg->response; + mbox->resp_offset = msg->resp_offset; + + if (mpfs_mbox_busy(mbox)) + return -EBUSY; + + if (msg->cmd_data_size) { + u32 index; + u8 extra_bits = msg->cmd_data_size & 3; + u32 *word_buf = (u32 *)msg->cmd_data; + + for (index = 0; index < (msg->cmd_data_size / 4); index++) + writel_relaxed(word_buf[index], + mbox->mbox_base + msg->mbox_offset + index * 0x4); + if (extra_bits) { + u8 i; + u8 byte_off = ALIGN_DOWN(msg->cmd_data_size, 4); + u8 *byte_buf = msg->cmd_data + byte_off; + + val = readl_relaxed(mbox->mbox_base + msg->mbox_offset + index * 0x4); + + for (i = 0u; i < extra_bits; i++) { + val &= ~(0xffu << (i * 8u)); + val |= (byte_buf[i] << (i * 8u)); + } + + writel_relaxed(val, mbox->mbox_base + msg->mbox_offset + index * 0x4); + } + } + + opt_sel = ((msg->mbox_offset << 7u) | (msg->cmd_opcode & 0x7fu)); + + tx_trigger = (opt_sel << SCB_CTRL_POS) & SCB_CTRL_MASK; + tx_trigger |= SCB_CTRL_REQ_MASK | SCB_STATUS_NOTIFY_MASK; + + if (mbox->control_scb) + regmap_write(mbox->control_scb, SERVICES_CR_OFFSET, tx_trigger); + else + writel_relaxed(tx_trigger, mbox->ctrl_base + SERVICES_CR_OFFSET); + + + return 0; +} + +static void mpfs_mbox_rx_data(struct mbox_chan *chan) +{ + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + struct mpfs_mss_response *response = mbox->response; + u16 num_words = ALIGN((response->resp_size), (4)) / 4U; + u32 i; + + if (!response->resp_msg) { + dev_err(mbox->dev, "failed to assign memory for response %d\n", -ENOMEM); + return; + } + + /* + * We should *never* get an interrupt while the controller is + * still in the busy state. If we do, something has gone badly + * wrong & the content of the mailbox would not be valid. + */ + if (mpfs_mbox_busy(mbox)) { + dev_err(mbox->dev, "got an interrupt but system controller is busy\n"); + response->resp_status = 0xDEAD; + return; + } + + for (i = 0; i < num_words; i++) { + response->resp_msg[i] = + readl_relaxed(mbox->mbox_base + + mbox->resp_offset + i * 0x4); + } + + mbox_chan_received_data(chan, response); +} + +static irqreturn_t mpfs_mbox_inbox_isr(int irq, void *data) +{ + struct mbox_chan *chan = data; + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + + if (mbox->sysreg_scb) + regmap_write(mbox->sysreg_scb, MESSAGE_INT_OFFSET, 0); + else + writel_relaxed(0, mbox->int_reg); + + mpfs_mbox_rx_data(chan); + + return IRQ_HANDLED; +} + +static int mpfs_mbox_startup(struct mbox_chan *chan) +{ + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + int ret = 0; + + if (!mbox) + return -EINVAL; + + ret = devm_request_irq(mbox->dev, mbox->irq, mpfs_mbox_inbox_isr, 0, "mpfs-mailbox", chan); + + return ret; +} + +static void mpfs_mbox_shutdown(struct mbox_chan *chan) +{ + struct mpfs_mbox *mbox = (struct mpfs_mbox *)chan->con_priv; + + devm_free_irq(mbox->dev, mbox->irq, chan); +} + +static const struct mbox_chan_ops mpfs_mbox_ops = { + .send_data = mpfs_mbox_send_data, + .startup = mpfs_mbox_startup, + .shutdown = mpfs_mbox_shutdown, + .last_tx_done = mpfs_mbox_last_tx_done, +}; + +static inline int mpfs_mbox_syscon_probe(struct mpfs_mbox *mbox, struct platform_device *pdev) +{ + mbox->control_scb = syscon_regmap_lookup_by_compatible("microchip,mpfs-control-scb"); + if (IS_ERR(mbox->control_scb)) + return PTR_ERR(mbox->control_scb); + + mbox->sysreg_scb = syscon_regmap_lookup_by_compatible("microchip,mpfs-sysreg-scb"); + if (IS_ERR(mbox->sysreg_scb)) + return PTR_ERR(mbox->sysreg_scb); + + mbox->mbox_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->mbox_base)) + return PTR_ERR(mbox->mbox_base); + + return 0; +} + +static inline int mpfs_mbox_old_format_probe(struct mpfs_mbox *mbox, struct platform_device *pdev) +{ + dev_warn(&pdev->dev, "falling back to old devicetree format"); + + mbox->ctrl_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->ctrl_base)) + return PTR_ERR(mbox->ctrl_base); + + mbox->int_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(mbox->int_reg)) + return PTR_ERR(mbox->int_reg); + + mbox->mbox_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(mbox->mbox_base)) // account for the old dt-binding w/ 2 regs + mbox->mbox_base = mbox->ctrl_base + MAILBOX_REG_OFFSET; + + return 0; +} + +static int mpfs_mbox_probe(struct platform_device *pdev) +{ + struct mpfs_mbox *mbox; + int ret; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + ret = mpfs_mbox_syscon_probe(mbox, pdev); + if (ret) { + /* + * set this to null, so it can be used as the decision for to + * regmap or not to regmap + */ + mbox->control_scb = NULL; + ret = mpfs_mbox_old_format_probe(mbox, pdev); + if (ret) + return ret; + } + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq < 0) + return mbox->irq; + + mbox->dev = &pdev->dev; + + mbox->chans[0].con_priv = mbox; + mbox->controller.dev = mbox->dev; + mbox->controller.num_chans = 1; + mbox->controller.chans = mbox->chans; + mbox->controller.ops = &mpfs_mbox_ops; + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 10u; + + ret = devm_mbox_controller_register(&pdev->dev, &mbox->controller); + if (ret) { + dev_err(&pdev->dev, "Registering MPFS mailbox controller failed\n"); + return ret; + } + dev_info(&pdev->dev, "Registered MPFS mailbox controller driver\n"); + + return 0; +} + +static const struct of_device_id mpfs_mbox_of_match[] = { + {.compatible = "microchip,mpfs-mailbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpfs_mbox_of_match); + +static struct platform_driver mpfs_mbox_driver = { + .driver = { + .name = "mpfs-mailbox", + .of_match_table = mpfs_mbox_of_match, + }, + .probe = mpfs_mbox_probe, +}; +module_platform_driver(mpfs_mbox_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("MPFS mailbox controller driver"); diff --git a/drivers/mailbox/mailbox-sti.c b/drivers/mailbox/mailbox-sti.c new file mode 100644 index 000000000..4fb5d1b55 --- /dev/null +++ b/drivers/mailbox/mailbox-sti.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STi Mailbox + * + * Copyright (C) 2015 ST Microelectronics + * + * Author: Lee Jones <lee.jones@linaro.org> for ST Microelectronics + * + * Based on the original driver written by; + * Alexandre Torgue, Olivier Lebreton and Loic Pallardy + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.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/property.h> +#include <linux/slab.h> + +#define STI_MBOX_INST_MAX 4 /* RAM saving: Max supported instances */ +#define STI_MBOX_CHAN_MAX 20 /* RAM saving: Max supported channels */ + +#define STI_IRQ_VAL_OFFSET 0x04 /* Read interrupt status */ +#define STI_IRQ_SET_OFFSET 0x24 /* Generate a Tx channel interrupt */ +#define STI_IRQ_CLR_OFFSET 0x44 /* Clear pending Rx interrupts */ +#define STI_ENA_VAL_OFFSET 0x64 /* Read enable status */ +#define STI_ENA_SET_OFFSET 0x84 /* Enable a channel */ +#define STI_ENA_CLR_OFFSET 0xa4 /* Disable a channel */ + +#define MBOX_BASE(mdev, inst) ((mdev)->base + ((inst) * 4)) + +/** + * struct sti_mbox_device - STi Mailbox device data + * + * @dev: Device to which it is attached + * @mbox: Representation of a communication channel controller + * @base: Base address of the register mapping region + * @name: Name of the mailbox + * @enabled: Local copy of enabled channels + * @lock: Mutex protecting enabled status + * + * An IP Mailbox is currently composed of 4 instances + * Each instance is currently composed of 32 channels + * This means that we have 128 channels per Mailbox + * A channel an be used for TX or RX + */ +struct sti_mbox_device { + struct device *dev; + struct mbox_controller *mbox; + void __iomem *base; + const char *name; + u32 enabled[STI_MBOX_INST_MAX]; + spinlock_t lock; +}; + +/** + * struct sti_mbox_pdata - STi Mailbox platform specific configuration + * + * @num_inst: Maximum number of instances in one HW Mailbox + * @num_chan: Maximum number of channel per instance + */ +struct sti_mbox_pdata { + unsigned int num_inst; + unsigned int num_chan; +}; + +/** + * struct sti_channel - STi Mailbox allocated channel information + * + * @mdev: Pointer to parent Mailbox device + * @instance: Instance number channel resides in + * @channel: Channel number pertaining to this container + */ +struct sti_channel { + struct sti_mbox_device *mdev; + unsigned int instance; + unsigned int channel; +}; + +static inline bool sti_mbox_channel_is_enabled(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + + return mdev->enabled[instance] & BIT(channel); +} + +static inline +struct mbox_chan *sti_mbox_to_channel(struct mbox_controller *mbox, + unsigned int instance, + unsigned int channel) +{ + struct sti_channel *chan_info; + int i; + + for (i = 0; i < mbox->num_chans; i++) { + chan_info = mbox->chans[i].con_priv; + if (chan_info && + chan_info->instance == instance && + chan_info->channel == channel) + return &mbox->chans[i]; + } + + dev_err(mbox->dev, + "Channel not registered: instance: %d channel: %d\n", + instance, channel); + + return NULL; +} + +static void sti_mbox_enable_channel(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + unsigned long flags; + void __iomem *base = MBOX_BASE(mdev, instance); + + spin_lock_irqsave(&mdev->lock, flags); + mdev->enabled[instance] |= BIT(channel); + writel_relaxed(BIT(channel), base + STI_ENA_SET_OFFSET); + spin_unlock_irqrestore(&mdev->lock, flags); +} + +static void sti_mbox_disable_channel(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + unsigned long flags; + void __iomem *base = MBOX_BASE(mdev, instance); + + spin_lock_irqsave(&mdev->lock, flags); + mdev->enabled[instance] &= ~BIT(channel); + writel_relaxed(BIT(channel), base + STI_ENA_CLR_OFFSET); + spin_unlock_irqrestore(&mdev->lock, flags); +} + +static void sti_mbox_clear_irq(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + void __iomem *base = MBOX_BASE(mdev, instance); + + writel_relaxed(BIT(channel), base + STI_IRQ_CLR_OFFSET); +} + +static struct mbox_chan *sti_mbox_irq_to_channel(struct sti_mbox_device *mdev, + unsigned int instance) +{ + struct mbox_controller *mbox = mdev->mbox; + struct mbox_chan *chan = NULL; + unsigned int channel; + unsigned long bits; + void __iomem *base = MBOX_BASE(mdev, instance); + + bits = readl_relaxed(base + STI_IRQ_VAL_OFFSET); + if (!bits) + /* No IRQs fired in specified instance */ + return NULL; + + /* An IRQ has fired, find the associated channel */ + for (channel = 0; bits; channel++) { + if (!test_and_clear_bit(channel, &bits)) + continue; + + chan = sti_mbox_to_channel(mbox, instance, channel); + if (chan) { + dev_dbg(mbox->dev, + "IRQ fired on instance: %d channel: %d\n", + instance, channel); + break; + } + } + + return chan; +} + +static irqreturn_t sti_mbox_thread_handler(int irq, void *data) +{ + struct sti_mbox_device *mdev = data; + struct sti_mbox_pdata *pdata = dev_get_platdata(mdev->dev); + struct mbox_chan *chan; + unsigned int instance; + + for (instance = 0; instance < pdata->num_inst; instance++) { +keep_looking: + chan = sti_mbox_irq_to_channel(mdev, instance); + if (!chan) + continue; + + mbox_chan_received_data(chan, NULL); + sti_mbox_clear_irq(chan); + sti_mbox_enable_channel(chan); + goto keep_looking; + } + + return IRQ_HANDLED; +} + +static irqreturn_t sti_mbox_irq_handler(int irq, void *data) +{ + struct sti_mbox_device *mdev = data; + struct sti_mbox_pdata *pdata = dev_get_platdata(mdev->dev); + struct sti_channel *chan_info; + struct mbox_chan *chan; + unsigned int instance; + int ret = IRQ_NONE; + + for (instance = 0; instance < pdata->num_inst; instance++) { + chan = sti_mbox_irq_to_channel(mdev, instance); + if (!chan) + continue; + chan_info = chan->con_priv; + + if (!sti_mbox_channel_is_enabled(chan)) { + dev_warn(mdev->dev, + "Unexpected IRQ: %s\n" + " instance: %d: channel: %d [enabled: %x]\n", + mdev->name, chan_info->instance, + chan_info->channel, mdev->enabled[instance]); + + /* Only handle IRQ if no other valid IRQs were found */ + if (ret == IRQ_NONE) + ret = IRQ_HANDLED; + continue; + } + + sti_mbox_disable_channel(chan); + ret = IRQ_WAKE_THREAD; + } + + if (ret == IRQ_NONE) + dev_err(mdev->dev, "Spurious IRQ - was a channel requested?\n"); + + return ret; +} + +static bool sti_mbox_tx_is_ready(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + void __iomem *base = MBOX_BASE(mdev, instance); + + if (!(readl_relaxed(base + STI_ENA_VAL_OFFSET) & BIT(channel))) { + dev_dbg(mdev->dev, "Mbox: %s: inst: %d, chan: %d disabled\n", + mdev->name, instance, channel); + return false; + } + + if (readl_relaxed(base + STI_IRQ_VAL_OFFSET) & BIT(channel)) { + dev_dbg(mdev->dev, "Mbox: %s: inst: %d, chan: %d not ready\n", + mdev->name, instance, channel); + return false; + } + + return true; +} + +static int sti_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct sti_channel *chan_info = chan->con_priv; + struct sti_mbox_device *mdev = chan_info->mdev; + unsigned int instance = chan_info->instance; + unsigned int channel = chan_info->channel; + void __iomem *base = MBOX_BASE(mdev, instance); + + /* Send event to co-processor */ + writel_relaxed(BIT(channel), base + STI_IRQ_SET_OFFSET); + + dev_dbg(mdev->dev, + "Sent via Mailbox %s: instance: %d channel: %d\n", + mdev->name, instance, channel); + + return 0; +} + +static int sti_mbox_startup_chan(struct mbox_chan *chan) +{ + sti_mbox_clear_irq(chan); + sti_mbox_enable_channel(chan); + + return 0; +} + +static void sti_mbox_shutdown_chan(struct mbox_chan *chan) +{ + struct sti_channel *chan_info = chan->con_priv; + struct mbox_controller *mbox = chan_info->mdev->mbox; + int i; + + for (i = 0; i < mbox->num_chans; i++) + if (chan == &mbox->chans[i]) + break; + + if (mbox->num_chans == i) { + dev_warn(mbox->dev, "Request to free non-existent channel\n"); + return; + } + + /* Reset channel */ + sti_mbox_disable_channel(chan); + sti_mbox_clear_irq(chan); + chan->con_priv = NULL; +} + +static struct mbox_chan *sti_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *spec) +{ + struct sti_mbox_device *mdev = dev_get_drvdata(mbox->dev); + struct sti_mbox_pdata *pdata = dev_get_platdata(mdev->dev); + struct sti_channel *chan_info; + struct mbox_chan *chan = NULL; + unsigned int instance = spec->args[0]; + unsigned int channel = spec->args[1]; + int i; + + /* Bounds checking */ + if (instance >= pdata->num_inst || channel >= pdata->num_chan) { + dev_err(mbox->dev, + "Invalid channel requested instance: %d channel: %d\n", + instance, channel); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < mbox->num_chans; i++) { + chan_info = mbox->chans[i].con_priv; + + /* Is requested channel free? */ + if (chan_info && + mbox->dev == chan_info->mdev->dev && + instance == chan_info->instance && + channel == chan_info->channel) { + + dev_err(mbox->dev, "Channel in use\n"); + return ERR_PTR(-EBUSY); + } + + /* + * Find the first free slot, then continue checking + * to see if requested channel is in use + */ + if (!chan && !chan_info) + chan = &mbox->chans[i]; + } + + if (!chan) { + dev_err(mbox->dev, "No free channels left\n"); + return ERR_PTR(-EBUSY); + } + + chan_info = devm_kzalloc(mbox->dev, sizeof(*chan_info), GFP_KERNEL); + if (!chan_info) + return ERR_PTR(-ENOMEM); + + chan_info->mdev = mdev; + chan_info->instance = instance; + chan_info->channel = channel; + + chan->con_priv = chan_info; + + dev_info(mbox->dev, + "Mbox: %s: Created channel: instance: %d channel: %d\n", + mdev->name, instance, channel); + + return chan; +} + +static const struct mbox_chan_ops sti_mbox_ops = { + .startup = sti_mbox_startup_chan, + .shutdown = sti_mbox_shutdown_chan, + .send_data = sti_mbox_send_data, + .last_tx_done = sti_mbox_tx_is_ready, +}; + +static const struct sti_mbox_pdata mbox_stih407_pdata = { + .num_inst = 4, + .num_chan = 32, +}; + +static const struct of_device_id sti_mailbox_match[] = { + { + .compatible = "st,stih407-mailbox", + .data = (void *)&mbox_stih407_pdata + }, + { } +}; +MODULE_DEVICE_TABLE(of, sti_mailbox_match); + +static int sti_mbox_probe(struct platform_device *pdev) +{ + struct mbox_controller *mbox; + struct sti_mbox_device *mdev; + struct device_node *np = pdev->dev.of_node; + struct mbox_chan *chans; + int irq; + int ret; + + pdev->dev.platform_data = (struct sti_mbox_pdata *)device_get_match_data(&pdev->dev); + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No configuration found\n"); + return -ENODEV; + } + + mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + platform_set_drvdata(pdev, mdev); + + mdev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mdev->base)) + return PTR_ERR(mdev->base); + + ret = of_property_read_string(np, "mbox-name", &mdev->name); + if (ret) + mdev->name = np->full_name; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + chans = devm_kcalloc(&pdev->dev, + STI_MBOX_CHAN_MAX, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + mdev->dev = &pdev->dev; + mdev->mbox = mbox; + + spin_lock_init(&mdev->lock); + + /* STi Mailbox does not have a Tx-Done or Tx-Ready IRQ */ + mbox->txdone_irq = false; + mbox->txdone_poll = true; + mbox->txpoll_period = 100; + mbox->ops = &sti_mbox_ops; + mbox->dev = mdev->dev; + mbox->of_xlate = sti_mbox_xlate; + mbox->chans = chans; + mbox->num_chans = STI_MBOX_CHAN_MAX; + + ret = devm_mbox_controller_register(&pdev->dev, mbox); + if (ret) + return ret; + + /* It's okay for Tx Mailboxes to not supply IRQs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_info(&pdev->dev, + "%s: Registered Tx only Mailbox\n", mdev->name); + return 0; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, + sti_mbox_irq_handler, + sti_mbox_thread_handler, + IRQF_ONESHOT, mdev->name, mdev); + if (ret) + return -EINVAL; + + dev_info(&pdev->dev, "%s: Registered Tx/Rx Mailbox\n", mdev->name); + + return 0; +} + +static struct platform_driver sti_mbox_driver = { + .probe = sti_mbox_probe, + .driver = { + .name = "sti-mailbox", + .of_match_table = sti_mailbox_match, + }, +}; +module_platform_driver(sti_mbox_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("STMicroelectronics Mailbox Controller"); +MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org"); +MODULE_ALIAS("platform:mailbox-sti"); diff --git a/drivers/mailbox/mailbox-test.c b/drivers/mailbox/mailbox-test.c new file mode 100644 index 000000000..7b6ef033e --- /dev/null +++ b/drivers/mailbox/mailbox-test.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015 ST Microelectronics + * + * Author: Lee Jones <lee.jones@linaro.org> + */ + +#include <linux/debugfs.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/sched/signal.h> + +#define MBOX_MAX_SIG_LEN 8 +#define MBOX_MAX_MSG_LEN 128 +#define MBOX_BYTES_PER_LINE 16 +#define MBOX_HEXDUMP_LINE_LEN ((MBOX_BYTES_PER_LINE * 4) + 2) +#define MBOX_HEXDUMP_MAX_LEN (MBOX_HEXDUMP_LINE_LEN * \ + (MBOX_MAX_MSG_LEN / MBOX_BYTES_PER_LINE)) + +struct mbox_test_device { + struct device *dev; + void __iomem *tx_mmio; + void __iomem *rx_mmio; + struct mbox_chan *tx_channel; + struct mbox_chan *rx_channel; + char *rx_buffer; + char *signal; + char *message; + spinlock_t lock; + struct mutex mutex; + wait_queue_head_t waitq; + bool data_ready; + struct fasync_struct *async_queue; + struct dentry *root_debugfs_dir; +}; + +static ssize_t mbox_test_signal_write(struct file *filp, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct mbox_test_device *tdev = filp->private_data; + + if (!tdev->tx_channel) { + dev_err(tdev->dev, "Channel cannot do Tx\n"); + return -EINVAL; + } + + if (count > MBOX_MAX_SIG_LEN) { + dev_err(tdev->dev, + "Signal length %zd greater than max allowed %d\n", + count, MBOX_MAX_SIG_LEN); + return -EINVAL; + } + + /* Only allocate memory if we need to */ + if (!tdev->signal) { + tdev->signal = kzalloc(MBOX_MAX_SIG_LEN, GFP_KERNEL); + if (!tdev->signal) + return -ENOMEM; + } + + if (copy_from_user(tdev->signal, userbuf, count)) { + kfree(tdev->signal); + tdev->signal = NULL; + return -EFAULT; + } + + return count; +} + +static const struct file_operations mbox_test_signal_ops = { + .write = mbox_test_signal_write, + .open = simple_open, + .llseek = generic_file_llseek, +}; + +static int mbox_test_message_fasync(int fd, struct file *filp, int on) +{ + struct mbox_test_device *tdev = filp->private_data; + + return fasync_helper(fd, filp, on, &tdev->async_queue); +} + +static ssize_t mbox_test_message_write(struct file *filp, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct mbox_test_device *tdev = filp->private_data; + char *message; + void *data; + int ret; + + if (!tdev->tx_channel) { + dev_err(tdev->dev, "Channel cannot do Tx\n"); + return -EINVAL; + } + + if (count > MBOX_MAX_MSG_LEN) { + dev_err(tdev->dev, + "Message length %zd greater than max allowed %d\n", + count, MBOX_MAX_MSG_LEN); + return -EINVAL; + } + + message = kzalloc(MBOX_MAX_MSG_LEN, GFP_KERNEL); + if (!message) + return -ENOMEM; + + mutex_lock(&tdev->mutex); + + tdev->message = message; + ret = copy_from_user(tdev->message, userbuf, count); + if (ret) { + ret = -EFAULT; + goto out; + } + + /* + * A separate signal is only of use if there is + * MMIO to subsequently pass the message through + */ + if (tdev->tx_mmio && tdev->signal) { + print_hex_dump_bytes("Client: Sending: Signal: ", DUMP_PREFIX_ADDRESS, + tdev->signal, MBOX_MAX_SIG_LEN); + + data = tdev->signal; + } else + data = tdev->message; + + print_hex_dump_bytes("Client: Sending: Message: ", DUMP_PREFIX_ADDRESS, + tdev->message, MBOX_MAX_MSG_LEN); + + ret = mbox_send_message(tdev->tx_channel, data); + if (ret < 0) + dev_err(tdev->dev, "Failed to send message via mailbox\n"); + +out: + kfree(tdev->signal); + kfree(tdev->message); + tdev->signal = NULL; + + mutex_unlock(&tdev->mutex); + + return ret < 0 ? ret : count; +} + +static bool mbox_test_message_data_ready(struct mbox_test_device *tdev) +{ + bool data_ready; + unsigned long flags; + + spin_lock_irqsave(&tdev->lock, flags); + data_ready = tdev->data_ready; + spin_unlock_irqrestore(&tdev->lock, flags); + + return data_ready; +} + +static ssize_t mbox_test_message_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct mbox_test_device *tdev = filp->private_data; + unsigned long flags; + char *touser, *ptr; + int l = 0; + int ret; + + DECLARE_WAITQUEUE(wait, current); + + touser = kzalloc(MBOX_HEXDUMP_MAX_LEN + 1, GFP_KERNEL); + if (!touser) + return -ENOMEM; + + if (!tdev->rx_channel) { + ret = snprintf(touser, 20, "<NO RX CAPABILITY>\n"); + ret = simple_read_from_buffer(userbuf, count, ppos, + touser, ret); + goto kfree_err; + } + + add_wait_queue(&tdev->waitq, &wait); + + do { + __set_current_state(TASK_INTERRUPTIBLE); + + if (mbox_test_message_data_ready(tdev)) + break; + + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto waitq_err; + } + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto waitq_err; + } + schedule(); + + } while (1); + + spin_lock_irqsave(&tdev->lock, flags); + + ptr = tdev->rx_buffer; + while (l < MBOX_HEXDUMP_MAX_LEN) { + hex_dump_to_buffer(ptr, + MBOX_BYTES_PER_LINE, + MBOX_BYTES_PER_LINE, 1, touser + l, + MBOX_HEXDUMP_LINE_LEN, true); + + ptr += MBOX_BYTES_PER_LINE; + l += MBOX_HEXDUMP_LINE_LEN; + *(touser + (l - 1)) = '\n'; + } + *(touser + l) = '\0'; + + memset(tdev->rx_buffer, 0, MBOX_MAX_MSG_LEN); + tdev->data_ready = false; + + spin_unlock_irqrestore(&tdev->lock, flags); + + ret = simple_read_from_buffer(userbuf, count, ppos, touser, MBOX_HEXDUMP_MAX_LEN); +waitq_err: + __set_current_state(TASK_RUNNING); + remove_wait_queue(&tdev->waitq, &wait); +kfree_err: + kfree(touser); + return ret; +} + +static __poll_t +mbox_test_message_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct mbox_test_device *tdev = filp->private_data; + + poll_wait(filp, &tdev->waitq, wait); + + if (mbox_test_message_data_ready(tdev)) + return EPOLLIN | EPOLLRDNORM; + return 0; +} + +static const struct file_operations mbox_test_message_ops = { + .write = mbox_test_message_write, + .read = mbox_test_message_read, + .fasync = mbox_test_message_fasync, + .poll = mbox_test_message_poll, + .open = simple_open, + .llseek = generic_file_llseek, +}; + +static int mbox_test_add_debugfs(struct platform_device *pdev, + struct mbox_test_device *tdev) +{ + if (!debugfs_initialized()) + return 0; + + tdev->root_debugfs_dir = debugfs_create_dir(dev_name(&pdev->dev), NULL); + if (IS_ERR(tdev->root_debugfs_dir)) { + dev_err(&pdev->dev, "Failed to create Mailbox debugfs\n"); + return -EINVAL; + } + + debugfs_create_file("message", 0600, tdev->root_debugfs_dir, + tdev, &mbox_test_message_ops); + + debugfs_create_file("signal", 0200, tdev->root_debugfs_dir, + tdev, &mbox_test_signal_ops); + + return 0; +} + +static void mbox_test_receive_message(struct mbox_client *client, void *message) +{ + struct mbox_test_device *tdev = dev_get_drvdata(client->dev); + unsigned long flags; + + spin_lock_irqsave(&tdev->lock, flags); + if (tdev->rx_mmio) { + memcpy_fromio(tdev->rx_buffer, tdev->rx_mmio, MBOX_MAX_MSG_LEN); + print_hex_dump_bytes("Client: Received [MMIO]: ", DUMP_PREFIX_ADDRESS, + tdev->rx_buffer, MBOX_MAX_MSG_LEN); + } else if (message) { + print_hex_dump_bytes("Client: Received [API]: ", DUMP_PREFIX_ADDRESS, + message, MBOX_MAX_MSG_LEN); + memcpy(tdev->rx_buffer, message, MBOX_MAX_MSG_LEN); + } + tdev->data_ready = true; + spin_unlock_irqrestore(&tdev->lock, flags); + + wake_up_interruptible(&tdev->waitq); + + kill_fasync(&tdev->async_queue, SIGIO, POLL_IN); +} + +static void mbox_test_prepare_message(struct mbox_client *client, void *message) +{ + struct mbox_test_device *tdev = dev_get_drvdata(client->dev); + + if (tdev->tx_mmio) { + if (tdev->signal) + memcpy_toio(tdev->tx_mmio, tdev->message, MBOX_MAX_MSG_LEN); + else + memcpy_toio(tdev->tx_mmio, message, MBOX_MAX_MSG_LEN); + } +} + +static void mbox_test_message_sent(struct mbox_client *client, + void *message, int r) +{ + if (r) + dev_warn(client->dev, + "Client: Message could not be sent: %d\n", r); + else + dev_info(client->dev, + "Client: Message sent\n"); +} + +static struct mbox_chan * +mbox_test_request_channel(struct platform_device *pdev, const char *name) +{ + struct mbox_client *client; + struct mbox_chan *channel; + + client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL); + if (!client) + return NULL; + + client->dev = &pdev->dev; + client->rx_callback = mbox_test_receive_message; + client->tx_prepare = mbox_test_prepare_message; + client->tx_done = mbox_test_message_sent; + client->tx_block = true; + client->knows_txdone = false; + client->tx_tout = 500; + + channel = mbox_request_channel_byname(client, name); + if (IS_ERR(channel)) { + dev_warn(&pdev->dev, "Failed to request %s channel\n", name); + return NULL; + } + + return channel; +} + +static void __iomem *mbox_test_ioremap(struct platform_device *pdev, unsigned int res_num) +{ + struct resource *res; + void __iomem *mmio; + + res = platform_get_resource(pdev, IORESOURCE_MEM, res_num); + if (!res) + return NULL; + + mmio = devm_ioremap_resource(&pdev->dev, res); + if (PTR_ERR(mmio) == -EBUSY) { + dev_info(&pdev->dev, "trying workaround with plain ioremap\n"); + return devm_ioremap(&pdev->dev, res->start, resource_size(res)); + } + + return IS_ERR(mmio) ? NULL : mmio; +} + +static int mbox_test_probe(struct platform_device *pdev) +{ + struct mbox_test_device *tdev; + int ret; + + tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL); + if (!tdev) + return -ENOMEM; + + tdev->dev = &pdev->dev; + spin_lock_init(&tdev->lock); + mutex_init(&tdev->mutex); + init_waitqueue_head(&tdev->waitq); + platform_set_drvdata(pdev, tdev); + + /* It's okay for MMIO to be NULL */ + tdev->tx_mmio = mbox_test_ioremap(pdev, 0); + + /* If specified, second reg entry is Rx MMIO */ + tdev->rx_mmio = mbox_test_ioremap(pdev, 1); + if (!tdev->rx_mmio) + tdev->rx_mmio = tdev->tx_mmio; + + tdev->tx_channel = mbox_test_request_channel(pdev, "tx"); + tdev->rx_channel = mbox_test_request_channel(pdev, "rx"); + + if (!tdev->tx_channel && !tdev->rx_channel) + return -EPROBE_DEFER; + + /* If Rx is not specified but has Rx MMIO, then Rx = Tx */ + if (!tdev->rx_channel && (tdev->rx_mmio != tdev->tx_mmio)) + tdev->rx_channel = tdev->tx_channel; + + if (tdev->rx_channel) { + tdev->rx_buffer = devm_kzalloc(&pdev->dev, + MBOX_MAX_MSG_LEN, GFP_KERNEL); + if (!tdev->rx_buffer) { + ret = -ENOMEM; + goto err_free_chans; + } + } + + ret = mbox_test_add_debugfs(pdev, tdev); + if (ret) + goto err_free_chans; + + dev_info(&pdev->dev, "Successfully registered\n"); + + return 0; + +err_free_chans: + if (tdev->tx_channel) + mbox_free_channel(tdev->tx_channel); + if (tdev->rx_channel && tdev->rx_channel != tdev->tx_channel) + mbox_free_channel(tdev->rx_channel); + return ret; +} + +static void mbox_test_remove(struct platform_device *pdev) +{ + struct mbox_test_device *tdev = platform_get_drvdata(pdev); + + debugfs_remove_recursive(tdev->root_debugfs_dir); + + if (tdev->tx_channel) + mbox_free_channel(tdev->tx_channel); + if (tdev->rx_channel && tdev->rx_channel != tdev->tx_channel) + mbox_free_channel(tdev->rx_channel); +} + +static const struct of_device_id mbox_test_match[] = { + { .compatible = "mailbox-test" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mbox_test_match); + +static struct platform_driver mbox_test_driver = { + .driver = { + .name = "mailbox_test", + .of_match_table = mbox_test_match, + }, + .probe = mbox_test_probe, + .remove = mbox_test_remove, +}; +module_platform_driver(mbox_test_driver); + +MODULE_DESCRIPTION("Generic Mailbox Testing Facility"); +MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/mailbox-th1520.c b/drivers/mailbox/mailbox-th1520.c new file mode 100644 index 000000000..626957c2e --- /dev/null +++ b/drivers/mailbox/mailbox-th1520.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Alibaba Group Holding Limited. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Status Register */ +#define TH_1520_MBOX_STA 0x0 +#define TH_1520_MBOX_CLR 0x4 +#define TH_1520_MBOX_MASK 0xc + +/* Transmit/receive data register: + * INFO0 ~ INFO6 + */ +#define TH_1520_MBOX_INFO_NUM 8 +#define TH_1520_MBOX_DATA_INFO_NUM 7 +#define TH_1520_MBOX_INFO0 0x14 +/* Transmit ack register: INFO7 */ +#define TH_1520_MBOX_INFO7 0x30 + +/* Generate remote icu IRQ Register */ +#define TH_1520_MBOX_GEN 0x10 +#define TH_1520_MBOX_GEN_RX_DATA BIT(6) +#define TH_1520_MBOX_GEN_TX_ACK BIT(7) + +#define TH_1520_MBOX_CHAN_RES_SIZE 0x1000 +#define TH_1520_MBOX_CHANS 4 +#define TH_1520_MBOX_CHAN_NAME_SIZE 20 + +#define TH_1520_MBOX_ACK_MAGIC 0xdeadbeaf + +#ifdef CONFIG_PM_SLEEP +/* store MBOX context across system-wide suspend/resume transitions */ +struct th1520_mbox_context { + u32 intr_mask[TH_1520_MBOX_CHANS]; +}; +#endif + +enum th1520_mbox_icu_cpu_id { + TH_1520_MBOX_ICU_KERNEL_CPU0, /* 910T */ + TH_1520_MBOX_ICU_CPU1, /* 902 */ + TH_1520_MBOX_ICU_CPU2, /* 906 */ + TH_1520_MBOX_ICU_CPU3, /* 910R */ +}; + +struct th1520_mbox_con_priv { + enum th1520_mbox_icu_cpu_id idx; + void __iomem *comm_local_base; + void __iomem *comm_remote_base; + char irq_desc[TH_1520_MBOX_CHAN_NAME_SIZE]; + struct mbox_chan *chan; +}; + +struct th1520_mbox_priv { + struct device *dev; + void __iomem *local_icu[TH_1520_MBOX_CHANS]; + void __iomem *remote_icu[TH_1520_MBOX_CHANS - 1]; + void __iomem *cur_cpu_ch_base; + spinlock_t mbox_lock; /* control register lock */ + + struct mbox_controller mbox; + struct mbox_chan mbox_chans[TH_1520_MBOX_CHANS]; + struct clk_bulk_data clocks[TH_1520_MBOX_CHANS]; + struct th1520_mbox_con_priv con_priv[TH_1520_MBOX_CHANS]; + int irq; +#ifdef CONFIG_PM_SLEEP + struct th1520_mbox_context *ctx; +#endif +}; + +static struct th1520_mbox_priv * +to_th1520_mbox_priv(struct mbox_controller *mbox) +{ + return container_of(mbox, struct th1520_mbox_priv, mbox); +} + +static void th1520_mbox_write(struct th1520_mbox_priv *priv, u32 val, u32 offs) +{ + iowrite32(val, priv->cur_cpu_ch_base + offs); +} + +static u32 th1520_mbox_read(struct th1520_mbox_priv *priv, u32 offs) +{ + return ioread32(priv->cur_cpu_ch_base + offs); +} + +static u32 th1520_mbox_rmw(struct th1520_mbox_priv *priv, u32 off, u32 set, + u32 clr) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->mbox_lock, flags); + val = th1520_mbox_read(priv, off); + val &= ~clr; + val |= set; + th1520_mbox_write(priv, val, off); + spin_unlock_irqrestore(&priv->mbox_lock, flags); + + return val; +} + +static void th1520_mbox_chan_write(struct th1520_mbox_con_priv *cp, u32 val, + u32 offs, bool is_remote) +{ + if (is_remote) + iowrite32(val, cp->comm_remote_base + offs); + else + iowrite32(val, cp->comm_local_base + offs); +} + +static u32 th1520_mbox_chan_read(struct th1520_mbox_con_priv *cp, u32 offs, + bool is_remote) +{ + if (is_remote) + return ioread32(cp->comm_remote_base + offs); + else + return ioread32(cp->comm_local_base + offs); +} + +static void th1520_mbox_chan_rmw(struct th1520_mbox_con_priv *cp, u32 off, + u32 set, u32 clr, bool is_remote) +{ + struct th1520_mbox_priv *priv = to_th1520_mbox_priv(cp->chan->mbox); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->mbox_lock, flags); + val = th1520_mbox_chan_read(cp, off, is_remote); + val &= ~clr; + val |= set; + th1520_mbox_chan_write(cp, val, off, is_remote); + spin_unlock_irqrestore(&priv->mbox_lock, flags); +} + +static void th1520_mbox_chan_rd_data(struct th1520_mbox_con_priv *cp, + void *data, bool is_remote) +{ + u32 off = TH_1520_MBOX_INFO0; + u32 *arg = data; + u32 i; + + /* read info0 ~ info6, totally 28 bytes + * requires data memory size is 28 bytes + */ + for (i = 0; i < TH_1520_MBOX_DATA_INFO_NUM; i++) { + *arg = th1520_mbox_chan_read(cp, off, is_remote); + off += 4; + arg++; + } +} + +static void th1520_mbox_chan_wr_data(struct th1520_mbox_con_priv *cp, + void *data, bool is_remote) +{ + u32 off = TH_1520_MBOX_INFO0; + u32 *arg = data; + u32 i; + + /* write info0 ~ info6, totally 28 bytes + * requires data memory is 28 bytes valid data + */ + for (i = 0; i < TH_1520_MBOX_DATA_INFO_NUM; i++) { + th1520_mbox_chan_write(cp, *arg, off, is_remote); + off += 4; + arg++; + } +} + +static void th1520_mbox_chan_wr_ack(struct th1520_mbox_con_priv *cp, void *data, + bool is_remote) +{ + u32 off = TH_1520_MBOX_INFO7; + u32 *arg = data; + + th1520_mbox_chan_write(cp, *arg, off, is_remote); +} + +static int th1520_mbox_chan_id_to_mapbit(struct th1520_mbox_con_priv *cp) +{ + int mapbit = 0; + int i; + + for (i = 0; i < TH_1520_MBOX_CHANS; i++) { + if (i == cp->idx) + return mapbit; + + if (i != TH_1520_MBOX_ICU_KERNEL_CPU0) + mapbit++; + } + + if (i == TH_1520_MBOX_CHANS) + dev_err(cp->chan->mbox->dev, "convert to mapbit failed\n"); + + return 0; +} + +static irqreturn_t th1520_mbox_isr(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox); + struct th1520_mbox_con_priv *cp = chan->con_priv; + int mapbit = th1520_mbox_chan_id_to_mapbit(cp); + u32 sta, dat[TH_1520_MBOX_DATA_INFO_NUM]; + u32 ack_magic = TH_1520_MBOX_ACK_MAGIC; + u32 info0_data, info7_data; + + sta = th1520_mbox_read(priv, TH_1520_MBOX_STA); + if (!(sta & BIT(mapbit))) + return IRQ_NONE; + + /* clear chan irq bit in STA register */ + th1520_mbox_rmw(priv, TH_1520_MBOX_CLR, BIT(mapbit), 0); + + /* info0 is the protocol word, should not be zero! */ + info0_data = th1520_mbox_chan_read(cp, TH_1520_MBOX_INFO0, false); + if (info0_data) { + /* read info0~info6 data */ + th1520_mbox_chan_rd_data(cp, dat, false); + + /* clear local info0 */ + th1520_mbox_chan_write(cp, 0x0, TH_1520_MBOX_INFO0, false); + + /* notify remote cpu */ + th1520_mbox_chan_wr_ack(cp, &ack_magic, true); + /* CPU1 902/906 use polling mode to monitor info7 */ + if (cp->idx != TH_1520_MBOX_ICU_CPU1 && + cp->idx != TH_1520_MBOX_ICU_CPU2) + th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, + TH_1520_MBOX_GEN_TX_ACK, 0, true); + + /* transfer the data to client */ + mbox_chan_received_data(chan, (void *)dat); + } + + /* info7 magic value mean the real ack signal, not generate bit7 */ + info7_data = th1520_mbox_chan_read(cp, TH_1520_MBOX_INFO7, false); + if (info7_data == TH_1520_MBOX_ACK_MAGIC) { + /* clear local info7 */ + th1520_mbox_chan_write(cp, 0x0, TH_1520_MBOX_INFO7, false); + + /* notify framework the last TX has completed */ + mbox_chan_txdone(chan, 0); + } + + if (!info0_data && !info7_data) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int th1520_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct th1520_mbox_con_priv *cp = chan->con_priv; + + th1520_mbox_chan_wr_data(cp, data, true); + th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, TH_1520_MBOX_GEN_RX_DATA, 0, + true); + return 0; +} + +static int th1520_mbox_startup(struct mbox_chan *chan) +{ + struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox); + struct th1520_mbox_con_priv *cp = chan->con_priv; + u32 data[8] = {}; + int mask_bit; + int ret; + + /* clear local and remote generate and info0~info7 */ + th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, 0x0, 0xff, true); + th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, 0x0, 0xff, false); + th1520_mbox_chan_wr_ack(cp, &data[7], true); + th1520_mbox_chan_wr_ack(cp, &data[7], false); + th1520_mbox_chan_wr_data(cp, &data[0], true); + th1520_mbox_chan_wr_data(cp, &data[0], false); + + /* enable the chan mask */ + mask_bit = th1520_mbox_chan_id_to_mapbit(cp); + th1520_mbox_rmw(priv, TH_1520_MBOX_MASK, BIT(mask_bit), 0); + + /* + * Mixing devm_ managed resources with manual IRQ handling is generally + * discouraged due to potential complexities with resource management, + * especially when dealing with shared interrupts. However, in this case, + * the approach is safe and effective because: + * + * 1. Each mailbox channel requests its IRQ within the .startup() callback + * and frees it within the .shutdown() callback. + * 2. During device unbinding, the devm_ managed mailbox controller first + * iterates through all channels, ensuring that their IRQs are freed before + * any other devm_ resources are released. + * + * This ordering guarantees that no interrupts can be triggered from the device + * while it is being unbound, preventing race conditions and ensuring system + * stability. + */ + ret = request_irq(priv->irq, th1520_mbox_isr, + IRQF_SHARED | IRQF_NO_SUSPEND, cp->irq_desc, chan); + if (ret) { + dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq); + return ret; + } + + return 0; +} + +static void th1520_mbox_shutdown(struct mbox_chan *chan) +{ + struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox); + struct th1520_mbox_con_priv *cp = chan->con_priv; + int mask_bit; + + free_irq(priv->irq, chan); + + /* clear the chan mask */ + mask_bit = th1520_mbox_chan_id_to_mapbit(cp); + th1520_mbox_rmw(priv, TH_1520_MBOX_MASK, 0, BIT(mask_bit)); +} + +static const struct mbox_chan_ops th1520_mbox_ops = { + .send_data = th1520_mbox_send_data, + .startup = th1520_mbox_startup, + .shutdown = th1520_mbox_shutdown, +}; + +static int th1520_mbox_init_generic(struct th1520_mbox_priv *priv) +{ +#ifdef CONFIG_PM_SLEEP + priv->ctx = devm_kzalloc(priv->dev, sizeof(*priv->ctx), GFP_KERNEL); + if (!priv->ctx) + return -ENOMEM; +#endif + /* Set default configuration */ + th1520_mbox_write(priv, 0xff, TH_1520_MBOX_CLR); + th1520_mbox_write(priv, 0x0, TH_1520_MBOX_MASK); + return 0; +} + +static struct mbox_chan *th1520_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + u32 chan; + + if (sp->args_count != 1) { + dev_err(mbox->dev, "Invalid argument count %d\n", + sp->args_count); + return ERR_PTR(-EINVAL); + } + + chan = sp->args[0]; /* comm remote channel */ + + if (chan >= mbox->num_chans) { + dev_err(mbox->dev, "Not supported channel number: %d\n", chan); + return ERR_PTR(-EINVAL); + } + + if (chan == TH_1520_MBOX_ICU_KERNEL_CPU0) { + dev_err(mbox->dev, "Cannot communicate with yourself\n"); + return ERR_PTR(-EINVAL); + } + + return &mbox->chans[chan]; +} + +static void __iomem *th1520_map_mmio(struct platform_device *pdev, + char *res_name, size_t offset) +{ + void __iomem *mapped; + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, res_name); + + if (!res) { + dev_err(&pdev->dev, "Failed to get resource: %s\n", res_name); + return ERR_PTR(-EINVAL); + } + + mapped = devm_ioremap(&pdev->dev, res->start + offset, + resource_size(res) - offset); + if (!mapped) { + dev_err(&pdev->dev, "Failed to map resource: %s\n", res_name); + return ERR_PTR(-ENOMEM); + } + + return mapped; +} + +static void th1520_disable_clk(void *data) +{ + struct th1520_mbox_priv *priv = data; + + clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks); +} + +static int th1520_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct th1520_mbox_priv *priv; + unsigned int remote_idx = 0; + unsigned int i; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->clocks[0].id = "clk-local"; + priv->clocks[1].id = "clk-remote-icu0"; + priv->clocks[2].id = "clk-remote-icu1"; + priv->clocks[3].id = "clk-remote-icu2"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks), + priv->clocks); + if (ret) { + dev_err(dev, "Failed to get clocks\n"); + return ret; + } + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks); + if (ret) { + dev_err(dev, "Failed to enable clocks\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, th1520_disable_clk, priv); + if (ret) + return ret; + + /* + * The address mappings in the device tree align precisely with those + * outlined in the manual. However, register offsets within these + * mapped regions are irregular, particularly for remote-icu0. + * Consequently, th1520_map_mmio() requires an additional parameter to + * handle this quirk. + */ + priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0] = + th1520_map_mmio(pdev, "local", 0x0); + if (IS_ERR(priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0])) + return PTR_ERR(priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0]); + + priv->remote_icu[0] = th1520_map_mmio(pdev, "remote-icu0", 0x4000); + if (IS_ERR(priv->remote_icu[0])) + return PTR_ERR(priv->remote_icu[0]); + + priv->remote_icu[1] = th1520_map_mmio(pdev, "remote-icu1", 0x0); + if (IS_ERR(priv->remote_icu[1])) + return PTR_ERR(priv->remote_icu[1]); + + priv->remote_icu[2] = th1520_map_mmio(pdev, "remote-icu2", 0x0); + if (IS_ERR(priv->remote_icu[2])) + return PTR_ERR(priv->remote_icu[2]); + + priv->local_icu[TH_1520_MBOX_ICU_CPU1] = + priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0] + + TH_1520_MBOX_CHAN_RES_SIZE; + priv->local_icu[TH_1520_MBOX_ICU_CPU2] = + priv->local_icu[TH_1520_MBOX_ICU_CPU1] + + TH_1520_MBOX_CHAN_RES_SIZE; + priv->local_icu[TH_1520_MBOX_ICU_CPU3] = + priv->local_icu[TH_1520_MBOX_ICU_CPU2] + + TH_1520_MBOX_CHAN_RES_SIZE; + + priv->cur_cpu_ch_base = priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0]; + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return priv->irq; + + /* init the chans */ + for (i = 0; i < TH_1520_MBOX_CHANS; i++) { + struct th1520_mbox_con_priv *cp = &priv->con_priv[i]; + + cp->idx = i; + cp->chan = &priv->mbox_chans[i]; + priv->mbox_chans[i].con_priv = cp; + snprintf(cp->irq_desc, sizeof(cp->irq_desc), + "th1520_mbox_chan[%i]", cp->idx); + + cp->comm_local_base = priv->local_icu[i]; + if (i != TH_1520_MBOX_ICU_KERNEL_CPU0) { + cp->comm_remote_base = priv->remote_icu[remote_idx]; + remote_idx++; + } + } + + spin_lock_init(&priv->mbox_lock); + + priv->mbox.dev = dev; + priv->mbox.ops = &th1520_mbox_ops; + priv->mbox.chans = priv->mbox_chans; + priv->mbox.num_chans = TH_1520_MBOX_CHANS; + priv->mbox.of_xlate = th1520_mbox_xlate; + priv->mbox.txdone_irq = true; + + platform_set_drvdata(pdev, priv); + + ret = th1520_mbox_init_generic(priv); + if (ret) { + dev_err(dev, "Failed to init mailbox context\n"); + return ret; + } + + return devm_mbox_controller_register(dev, &priv->mbox); +} + +static const struct of_device_id th1520_mbox_dt_ids[] = { + { .compatible = "thead,th1520-mbox" }, + {} +}; +MODULE_DEVICE_TABLE(of, th1520_mbox_dt_ids); + +#ifdef CONFIG_PM_SLEEP +static int __maybe_unused th1520_mbox_suspend_noirq(struct device *dev) +{ + struct th1520_mbox_priv *priv = dev_get_drvdata(dev); + struct th1520_mbox_context *ctx = priv->ctx; + u32 i; + /* + * ONLY interrupt mask bit should be stored and restores. + * INFO data all assumed to be lost. + */ + for (i = 0; i < TH_1520_MBOX_CHANS; i++) { + ctx->intr_mask[i] = + ioread32(priv->local_icu[i] + TH_1520_MBOX_MASK); + } + return 0; +} + +static int __maybe_unused th1520_mbox_resume_noirq(struct device *dev) +{ + struct th1520_mbox_priv *priv = dev_get_drvdata(dev); + struct th1520_mbox_context *ctx = priv->ctx; + u32 i; + + for (i = 0; i < TH_1520_MBOX_CHANS; i++) { + iowrite32(ctx->intr_mask[i], + priv->local_icu[i] + TH_1520_MBOX_MASK); + } + + return 0; +} +#endif + +static int __maybe_unused th1520_mbox_runtime_suspend(struct device *dev) +{ + struct th1520_mbox_priv *priv = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks); + + return 0; +} + +static int __maybe_unused th1520_mbox_runtime_resume(struct device *dev) +{ + struct th1520_mbox_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks); + if (ret) + dev_err(dev, "Failed to enable clocks in runtime resume\n"); + + return ret; +} + +static const struct dev_pm_ops th1520_mbox_pm_ops = { +#ifdef CONFIG_PM_SLEEP + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(th1520_mbox_suspend_noirq, + th1520_mbox_resume_noirq) +#endif + SET_RUNTIME_PM_OPS(th1520_mbox_runtime_suspend, + th1520_mbox_runtime_resume, NULL) +}; + +static struct platform_driver th1520_mbox_driver = { + .probe = th1520_mbox_probe, + .driver = { + .name = "th1520-mbox", + .of_match_table = th1520_mbox_dt_ids, + .pm = &th1520_mbox_pm_ops, + }, +}; +module_platform_driver(th1520_mbox_driver); + +MODULE_DESCRIPTION("Thead TH-1520 mailbox IPC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/mailbox-xgene-slimpro.c b/drivers/mailbox/mailbox-xgene-slimpro.c new file mode 100644 index 000000000..946ea773e --- /dev/null +++ b/drivers/mailbox/mailbox-xgene-slimpro.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APM X-Gene SLIMpro MailBox Driver + * + * Copyright (c) 2015, Applied Micro Circuits Corporation + * Author: Feng Kan fkan@apm.com + */ +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define MBOX_CON_NAME "slimpro-mbox" +#define MBOX_REG_SET_OFFSET 0x1000 +#define MBOX_CNT 8 +#define MBOX_STATUS_AVAIL_MASK BIT(16) +#define MBOX_STATUS_ACK_MASK BIT(0) + +/* Configuration and Status Registers */ +#define REG_DB_IN 0x00 +#define REG_DB_DIN0 0x04 +#define REG_DB_DIN1 0x08 +#define REG_DB_OUT 0x10 +#define REG_DB_DOUT0 0x14 +#define REG_DB_DOUT1 0x18 +#define REG_DB_STAT 0x20 +#define REG_DB_STATMASK 0x24 + +/** + * X-Gene SlimPRO mailbox channel information + * + * @dev: Device to which it is attached + * @chan: Pointer to mailbox communication channel + * @reg: Base address to access channel registers + * @irq: Interrupt number of the channel + * @rx_msg: Received message storage + */ +struct slimpro_mbox_chan { + struct device *dev; + struct mbox_chan *chan; + void __iomem *reg; + int irq; + u32 rx_msg[3]; +}; + +/** + * X-Gene SlimPRO Mailbox controller data + * + * X-Gene SlimPRO Mailbox controller has 8 communication channels. + * Each channel has a separate IRQ number assigned to it. + * + * @mb_ctrl: Representation of the communication channel controller + * @mc: Array of SlimPRO mailbox channels of the controller + * @chans: Array of mailbox communication channels + * + */ +struct slimpro_mbox { + struct mbox_controller mb_ctrl; + struct slimpro_mbox_chan mc[MBOX_CNT]; + struct mbox_chan chans[MBOX_CNT]; +}; + +static void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg) +{ + writel(msg[1], mb_chan->reg + REG_DB_DOUT0); + writel(msg[2], mb_chan->reg + REG_DB_DOUT1); + writel(msg[0], mb_chan->reg + REG_DB_OUT); +} + +static void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan) +{ + mb_chan->rx_msg[1] = readl(mb_chan->reg + REG_DB_DIN0); + mb_chan->rx_msg[2] = readl(mb_chan->reg + REG_DB_DIN1); + mb_chan->rx_msg[0] = readl(mb_chan->reg + REG_DB_IN); +} + +static int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan) +{ + u32 val = readl(mb_chan->reg + REG_DB_STAT); + + if (val & MBOX_STATUS_ACK_MASK) { + writel(MBOX_STATUS_ACK_MASK, mb_chan->reg + REG_DB_STAT); + return 1; + } + return 0; +} + +static int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan) +{ + u32 val = readl(mb_chan->reg + REG_DB_STAT); + + if (val & MBOX_STATUS_AVAIL_MASK) { + mb_chan_recv_msg(mb_chan); + writel(MBOX_STATUS_AVAIL_MASK, mb_chan->reg + REG_DB_STAT); + return 1; + } + return 0; +} + +static irqreturn_t slimpro_mbox_irq(int irq, void *id) +{ + struct slimpro_mbox_chan *mb_chan = id; + + if (mb_chan_status_ack(mb_chan)) + mbox_chan_txdone(mb_chan->chan, 0); + + if (mb_chan_status_avail(mb_chan)) + mbox_chan_received_data(mb_chan->chan, mb_chan->rx_msg); + + return IRQ_HANDLED; +} + +static int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg) +{ + struct slimpro_mbox_chan *mb_chan = chan->con_priv; + + mb_chan_send_msg(mb_chan, msg); + return 0; +} + +static int slimpro_mbox_startup(struct mbox_chan *chan) +{ + struct slimpro_mbox_chan *mb_chan = chan->con_priv; + int rc; + u32 val; + + rc = devm_request_irq(mb_chan->dev, mb_chan->irq, slimpro_mbox_irq, 0, + MBOX_CON_NAME, mb_chan); + if (unlikely(rc)) { + dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n", + mb_chan->irq); + return rc; + } + + /* Enable HW interrupt */ + writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK, + mb_chan->reg + REG_DB_STAT); + /* Unmask doorbell status interrupt */ + val = readl(mb_chan->reg + REG_DB_STATMASK); + val &= ~(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); + writel(val, mb_chan->reg + REG_DB_STATMASK); + + return 0; +} + +static void slimpro_mbox_shutdown(struct mbox_chan *chan) +{ + struct slimpro_mbox_chan *mb_chan = chan->con_priv; + u32 val; + + /* Mask doorbell status interrupt */ + val = readl(mb_chan->reg + REG_DB_STATMASK); + val |= (MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK); + writel(val, mb_chan->reg + REG_DB_STATMASK); + + devm_free_irq(mb_chan->dev, mb_chan->irq, mb_chan); +} + +static const struct mbox_chan_ops slimpro_mbox_ops = { + .send_data = slimpro_mbox_send_data, + .startup = slimpro_mbox_startup, + .shutdown = slimpro_mbox_shutdown, +}; + +static int slimpro_mbox_probe(struct platform_device *pdev) +{ + struct slimpro_mbox *ctx; + void __iomem *mb_base; + int rc; + int i; + + ctx = devm_kzalloc(&pdev->dev, sizeof(struct slimpro_mbox), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + platform_set_drvdata(pdev, ctx); + + mb_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mb_base)) + return PTR_ERR(mb_base); + + /* Setup mailbox links */ + for (i = 0; i < MBOX_CNT; i++) { + ctx->mc[i].irq = platform_get_irq(pdev, i); + if (ctx->mc[i].irq < 0) { + if (i == 0) { + dev_err(&pdev->dev, "no available IRQ\n"); + return -EINVAL; + } + dev_info(&pdev->dev, "no IRQ for channel %d\n", i); + break; + } + + ctx->mc[i].dev = &pdev->dev; + ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET; + ctx->mc[i].chan = &ctx->chans[i]; + ctx->chans[i].con_priv = &ctx->mc[i]; + } + + /* Setup mailbox controller */ + ctx->mb_ctrl.dev = &pdev->dev; + ctx->mb_ctrl.chans = ctx->chans; + ctx->mb_ctrl.txdone_irq = true; + ctx->mb_ctrl.ops = &slimpro_mbox_ops; + ctx->mb_ctrl.num_chans = i; + + rc = devm_mbox_controller_register(&pdev->dev, &ctx->mb_ctrl); + if (rc) { + dev_err(&pdev->dev, + "APM X-Gene SLIMpro MailBox register failed:%d\n", rc); + return rc; + } + + dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n"); + return 0; +} + +static const struct of_device_id slimpro_of_match[] = { + {.compatible = "apm,xgene-slimpro-mbox" }, + { }, +}; +MODULE_DEVICE_TABLE(of, slimpro_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id slimpro_acpi_ids[] = { + {"APMC0D01", 0}, + {} +}; +MODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids); +#endif + +static struct platform_driver slimpro_mbox_driver = { + .probe = slimpro_mbox_probe, + .driver = { + .name = "xgene-slimpro-mbox", + .of_match_table = of_match_ptr(slimpro_of_match), + .acpi_match_table = ACPI_PTR(slimpro_acpi_ids) + }, +}; + +static int __init slimpro_mbox_init(void) +{ + return platform_driver_register(&slimpro_mbox_driver); +} + +static void __exit slimpro_mbox_exit(void) +{ + platform_driver_unregister(&slimpro_mbox_driver); +} + +subsys_initcall(slimpro_mbox_init); +module_exit(slimpro_mbox_exit); + +MODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c new file mode 100644 index 000000000..efacd24a0 --- /dev/null +++ b/drivers/mailbox/mailbox.c @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Mailbox: Common code for Mailbox controllers and users + * + * Copyright (C) 2013-2014 Linaro Ltd. + * Author: Jassi Brar <jassisinghbrar@gmail.com> + */ + +#include <linux/cleanup.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/mailbox_client.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/property.h> +#include <linux/seq_file.h> +#include <linux/spinlock.h> + +static LIST_HEAD(mbox_cons); +static DEFINE_MUTEX(con_mutex); + +static int add_to_rbuf(struct mbox_chan *chan, void *mssg) +{ + int idx; + + guard(spinlock_irqsave)(&chan->lock); + + /* See if there is any space left */ + if (chan->msg_count == MBOX_TX_QUEUE_LEN) + return -ENOBUFS; + + idx = chan->msg_free; + chan->msg_data[idx] = mssg; + chan->msg_count++; + + if (idx == MBOX_TX_QUEUE_LEN - 1) + chan->msg_free = 0; + else + chan->msg_free++; + + return idx; +} + +static void msg_submit(struct mbox_chan *chan) +{ + unsigned count, idx; + void *data; + int err = -EBUSY; + + scoped_guard(spinlock_irqsave, &chan->lock) { + if (!chan->msg_count || chan->active_req != MBOX_NO_MSG) + break; + + count = chan->msg_count; + idx = chan->msg_free; + if (idx >= count) + idx -= count; + else + idx += MBOX_TX_QUEUE_LEN - count; + + data = chan->msg_data[idx]; + + if (chan->cl->tx_prepare) + chan->cl->tx_prepare(chan->cl, data); + /* Try to submit a message to the MBOX controller */ + err = chan->mbox->ops->send_data(chan, data); + if (!err) { + chan->active_req = data; + chan->msg_count--; + } + } + + if (!err && (chan->txdone_method & MBOX_TXDONE_BY_POLL)) { + /* kick start the timer immediately to avoid delays */ + scoped_guard(spinlock_irqsave, &chan->mbox->poll_hrt_lock) + hrtimer_start(&chan->mbox->poll_hrt, 0, HRTIMER_MODE_REL); + } +} + +static void tx_tick(struct mbox_chan *chan, int r) +{ + void *mssg; + + scoped_guard(spinlock_irqsave, &chan->lock) { + mssg = chan->active_req; + chan->active_req = MBOX_NO_MSG; + } + + /* Submit next message */ + msg_submit(chan); + + if (mssg == MBOX_NO_MSG) + return; + + /* Notify the client */ + if (chan->cl->tx_done) + chan->cl->tx_done(chan->cl, mssg, r); + + if (r != -ETIME && chan->cl->tx_block) { + chan->tx_status = r; + complete(&chan->tx_complete); + } +} + +static enum hrtimer_restart txdone_hrtimer(struct hrtimer *hrtimer) +{ + struct mbox_controller *mbox = + container_of(hrtimer, struct mbox_controller, poll_hrt); + bool txdone, resched = false; + int i; + + for (i = 0; i < mbox->num_chans; i++) { + struct mbox_chan *chan = &mbox->chans[i]; + + if (chan->active_req != MBOX_NO_MSG && chan->cl) { + txdone = chan->mbox->ops->last_tx_done(chan); + if (txdone) + tx_tick(chan, 0); + else + resched = true; + } + } + + if (resched) { + scoped_guard(spinlock_irqsave, &mbox->poll_hrt_lock) { + if (!hrtimer_is_queued(hrtimer)) + hrtimer_forward_now(hrtimer, ms_to_ktime(mbox->txpoll_period)); + } + + return HRTIMER_RESTART; + } + return HRTIMER_NORESTART; +} + +/** + * mbox_chan_received_data - A way for controller driver to push data + * received from remote to the upper layer. + * @chan: Pointer to the mailbox channel on which RX happened. + * @mssg: Client specific message typecasted as void * + * + * After startup and before shutdown any data received on the chan + * is passed on to the API via atomic mbox_chan_received_data(). + * The controller should ACK the RX only after this call returns. + */ +void mbox_chan_received_data(struct mbox_chan *chan, void *mssg) +{ + /* No buffering the received data */ + if (chan->cl->rx_callback) + chan->cl->rx_callback(chan->cl, mssg); +} +EXPORT_SYMBOL_GPL(mbox_chan_received_data); + +/** + * mbox_chan_txdone - A way for controller driver to notify the + * framework that the last TX has completed. + * @chan: Pointer to the mailbox chan on which TX happened. + * @r: Status of last TX - OK or ERROR + * + * The controller that has IRQ for TX ACK calls this atomic API + * to tick the TX state machine. It works only if txdone_irq + * is set by the controller. + */ +void mbox_chan_txdone(struct mbox_chan *chan, int r) +{ + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_IRQ))) { + dev_err(chan->mbox->dev, + "Controller can't run the TX ticker\n"); + return; + } + + tx_tick(chan, r); +} +EXPORT_SYMBOL_GPL(mbox_chan_txdone); + +/** + * mbox_client_txdone - The way for a client to run the TX state machine. + * @chan: Mailbox channel assigned to this client. + * @r: Success status of last transmission. + * + * The client/protocol had received some 'ACK' packet and it notifies + * the API that the last packet was sent successfully. This only works + * if the controller can't sense TX-Done. + */ +void mbox_client_txdone(struct mbox_chan *chan, int r) +{ + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_ACK))) { + dev_err(chan->mbox->dev, "Client can't run the TX ticker\n"); + return; + } + + tx_tick(chan, r); +} +EXPORT_SYMBOL_GPL(mbox_client_txdone); + +/** + * mbox_client_peek_data - A way for client driver to pull data + * received from remote by the controller. + * @chan: Mailbox channel assigned to this client. + * + * A poke to controller driver for any received data. + * The data is actually passed onto client via the + * mbox_chan_received_data() + * The call can be made from atomic context, so the controller's + * implementation of peek_data() must not sleep. + * + * Return: True, if controller has, and is going to push after this, + * some data. + * False, if controller doesn't have any data to be read. + */ +bool mbox_client_peek_data(struct mbox_chan *chan) +{ + if (chan->mbox->ops->peek_data) + return chan->mbox->ops->peek_data(chan); + + return false; +} +EXPORT_SYMBOL_GPL(mbox_client_peek_data); + +/** + * mbox_chan_tx_slots_available - Query the number of available TX queue slots. + * @chan: Mailbox channel to query. + * + * Clients may call this to check how many messages can be queued via + * mbox_send_message() before the channel's TX queue is full. This helps + * clients avoid the -ENOBUFS error without needing to increase + * MBOX_TX_QUEUE_LEN. + * This can be called from atomic context. + * + * Return: Number of available slots in the channel's TX queue. + */ +unsigned int mbox_chan_tx_slots_available(struct mbox_chan *chan) +{ + unsigned int ret; + + guard(spinlock_irqsave)(&chan->lock); + ret = MBOX_TX_QUEUE_LEN - chan->msg_count; + + return ret; +} +EXPORT_SYMBOL_GPL(mbox_chan_tx_slots_available); + +/** + * mbox_send_message - For client to submit a message to be + * sent to the remote. + * @chan: Mailbox channel assigned to this client. + * @mssg: Client specific message typecasted. + * + * For client to submit data to the controller destined for a remote + * processor. If the client had set 'tx_block', the call will return + * either when the remote receives the data or when 'tx_tout' millisecs + * run out. + * In non-blocking mode, the requests are buffered by the API and a + * non-negative token is returned for each queued request. If the request + * is not queued, a negative token is returned. Upon failure or successful + * TX, the API calls 'tx_done' from atomic context, from which the client + * could submit yet another request. + * The pointer to message should be preserved until it is sent + * over the chan, i.e, tx_done() is made. + * This function could be called from atomic context as it simply + * queues the data and returns a token against the request. + * In blocking mode, it is caller's responsibility to serialize threads' + * access to a channel if multi-threads are to send messages through the + * same channel, i.e. caller should not call this function until any + * previous call returns. + * + * Return: Non-negative integer for successful submission (non-blocking mode) + * or transmission over chan (blocking mode). + * Negative value denotes failure. + */ +int mbox_send_message(struct mbox_chan *chan, void *mssg) +{ + int t; + + if (!chan || !chan->cl || mssg == MBOX_NO_MSG) + return -EINVAL; + + t = add_to_rbuf(chan, mssg); + if (t < 0) { + dev_err(chan->mbox->dev, "Try increasing MBOX_TX_QUEUE_LEN\n"); + return t; + } + + msg_submit(chan); + + if (chan->cl->tx_block) { + unsigned long wait; + int ret; + + if (!chan->cl->tx_tout) /* wait forever */ + wait = msecs_to_jiffies(3600000); + else + wait = msecs_to_jiffies(chan->cl->tx_tout); + + ret = wait_for_completion_timeout(&chan->tx_complete, wait); + if (ret == 0) { + t = -ETIME; + tx_tick(chan, t); + } else if (chan->tx_status < 0) { + t = chan->tx_status; + } + } + + return t; +} +EXPORT_SYMBOL_GPL(mbox_send_message); + +/** + * mbox_flush - flush a mailbox channel + * @chan: mailbox channel to flush + * @timeout: time, in milliseconds, to allow the flush operation to succeed + * + * Mailbox controllers that need to work in atomic context can implement the + * ->flush() callback to busy loop until a transmission has been completed. + * The implementation must call mbox_chan_txdone() upon success. Clients can + * call the mbox_flush() function at any time after mbox_send_message() to + * flush the transmission. After the function returns success, the mailbox + * transmission is guaranteed to have completed. + * + * Returns: 0 on success or a negative error code on failure. + */ +int mbox_flush(struct mbox_chan *chan, unsigned long timeout) +{ + int ret; + + if (!chan->mbox->ops->flush) + return -ENOTSUPP; + + ret = chan->mbox->ops->flush(chan, timeout); + if (ret < 0) + tx_tick(chan, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(mbox_flush); + +static void mbox_clean_and_put_channel(struct mbox_chan *chan) +{ + /* The queued TX requests are simply aborted, no callbacks are made */ + scoped_guard(spinlock_irqsave, &chan->lock) { + chan->cl = NULL; + chan->active_req = MBOX_NO_MSG; + if (chan->txdone_method == MBOX_TXDONE_BY_ACK) + chan->txdone_method = MBOX_TXDONE_BY_POLL; + } + + module_put(chan->mbox->dev->driver->owner); +} + +static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) +{ + struct device *dev = cl->dev; + int ret; + + if (chan->cl || !try_module_get(chan->mbox->dev->driver->owner)) { + dev_err(dev, "%s: mailbox not free\n", __func__); + return -EBUSY; + } + + scoped_guard(spinlock_irqsave, &chan->lock) { + chan->msg_free = 0; + chan->msg_count = 0; + chan->active_req = MBOX_NO_MSG; + chan->cl = cl; + init_completion(&chan->tx_complete); + + if (chan->txdone_method == MBOX_TXDONE_BY_POLL && cl->knows_txdone) + chan->txdone_method = MBOX_TXDONE_BY_ACK; + } + + if (chan->mbox->ops->startup) { + ret = chan->mbox->ops->startup(chan); + if (ret) { + dev_err(dev, "Unable to startup the chan (%d)\n", ret); + mbox_clean_and_put_channel(chan); + return ret; + } + } + + return 0; +} + +/** + * mbox_bind_client - Bind client to a mailbox channel. + * @chan: The mailbox channel to bind the client to. + * @cl: Identity of the client requesting the channel. + * + * The Client specifies its requirements and capabilities while asking for + * a mailbox channel. It can't be called from atomic context. + * The channel is exclusively allocated and can't be used by another + * client before the owner calls mbox_free_channel. + * After assignment, any packet received on this channel will be + * handed over to the client via the 'rx_callback'. + * The framework holds reference to the client, so the mbox_client + * structure shouldn't be modified until the mbox_free_channel returns. + * + * Return: 0 if the channel was assigned to the client successfully. + * <0 for request failure. + */ +int mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) +{ + guard(mutex)(&con_mutex); + + return __mbox_bind_client(chan, cl); +} +EXPORT_SYMBOL_GPL(mbox_bind_client); + +/** + * mbox_request_channel - Request a mailbox channel. + * @cl: Identity of the client requesting the channel. + * @index: Index of mailbox specifier in 'mboxes' property. + * + * The Client specifies its requirements and capabilities while asking for + * a mailbox channel. It can't be called from atomic context. + * The channel is exclusively allocated and can't be used by another + * client before the owner calls mbox_free_channel. + * After assignment, any packet received on this channel will be + * handed over to the client via the 'rx_callback'. + * The framework holds reference to the client, so the mbox_client + * structure shouldn't be modified until the mbox_free_channel returns. + * + * Return: Pointer to the channel assigned to the client if successful. + * ERR_PTR for request failure. + */ +struct mbox_chan *mbox_request_channel(struct mbox_client *cl, int index) +{ + struct fwnode_reference_args fwspec; + struct fwnode_handle *fwnode; + struct mbox_controller *mbox; + struct of_phandle_args spec; + struct mbox_chan *chan; + struct device *dev; + unsigned int i; + int ret; + + dev = cl->dev; + if (!dev) { + pr_debug("No owner device\n"); + return ERR_PTR(-ENODEV); + } + + fwnode = dev_fwnode(dev); + if (!fwnode) { + dev_dbg(dev, "No owner fwnode\n"); + return ERR_PTR(-ENODEV); + } + + ret = fwnode_property_get_reference_args(fwnode, "mboxes", "#mbox-cells", + 0, index, &fwspec); + if (ret) { + dev_err(dev, "%s: can't parse \"%s\" property\n", __func__, "mboxes"); + return ERR_PTR(ret); + } + + spec.np = to_of_node(fwspec.fwnode); + spec.args_count = fwspec.nargs; + for (i = 0; i < spec.args_count; i++) + spec.args[i] = fwspec.args[i]; + + scoped_guard(mutex, &con_mutex) { + chan = ERR_PTR(-EPROBE_DEFER); + list_for_each_entry(mbox, &mbox_cons, node) { + if (device_match_fwnode(mbox->dev, fwspec.fwnode)) { + if (mbox->fw_xlate) { + chan = mbox->fw_xlate(mbox, &fwspec); + if (!IS_ERR(chan)) + break; + } else if (mbox->of_xlate) { + chan = mbox->of_xlate(mbox, &spec); + if (!IS_ERR(chan)) + break; + } + } + } + + fwnode_handle_put(fwspec.fwnode); + + if (IS_ERR(chan)) + return chan; + + ret = __mbox_bind_client(chan, cl); + if (ret) + chan = ERR_PTR(ret); + } + + return chan; +} +EXPORT_SYMBOL_GPL(mbox_request_channel); + +struct mbox_chan *mbox_request_channel_byname(struct mbox_client *cl, + const char *name) +{ + int index = device_property_match_string(cl->dev, "mbox-names", name); + + if (index < 0) { + dev_err(cl->dev, "%s() could not locate channel named \"%s\"\n", + __func__, name); + return ERR_PTR(index); + } + return mbox_request_channel(cl, index); +} +EXPORT_SYMBOL_GPL(mbox_request_channel_byname); + +/** + * mbox_free_channel - The client relinquishes control of a mailbox + * channel by this call. + * @chan: The mailbox channel to be freed. + */ +void mbox_free_channel(struct mbox_chan *chan) +{ + if (!chan || !chan->cl) + return; + + if (chan->mbox->ops->shutdown) + chan->mbox->ops->shutdown(chan); + + mbox_clean_and_put_channel(chan); +} +EXPORT_SYMBOL_GPL(mbox_free_channel); + +static struct mbox_chan *fw_mbox_index_xlate(struct mbox_controller *mbox, + const struct fwnode_reference_args *sp) +{ + if (sp->nargs < 1 || sp->args[0] >= mbox->num_chans) + return ERR_PTR(-EINVAL); + + return &mbox->chans[sp->args[0]]; +} + +/** + * mbox_controller_register - Register the mailbox controller + * @mbox: Pointer to the mailbox controller. + * + * The controller driver registers its communication channels + */ +int mbox_controller_register(struct mbox_controller *mbox) +{ + int i, txdone; + + if (!mbox || !mbox->dev || !mbox->ops || !mbox->chans || !mbox->num_chans) + return -EINVAL; + + if (mbox->txdone_irq) + txdone = MBOX_TXDONE_BY_IRQ; + else if (mbox->txdone_poll) + txdone = MBOX_TXDONE_BY_POLL; + else /* It has to be ACK then */ + txdone = MBOX_TXDONE_BY_ACK; + + if (txdone == MBOX_TXDONE_BY_POLL) { + + if (!mbox->ops->last_tx_done) { + dev_err(mbox->dev, "last_tx_done method is absent\n"); + return -EINVAL; + } + + hrtimer_setup(&mbox->poll_hrt, txdone_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + spin_lock_init(&mbox->poll_hrt_lock); + } + + for (i = 0; i < mbox->num_chans; i++) { + struct mbox_chan *chan = &mbox->chans[i]; + + chan->cl = NULL; + chan->mbox = mbox; + chan->active_req = MBOX_NO_MSG; + chan->txdone_method = txdone; + spin_lock_init(&chan->lock); + } + + if (!mbox->fw_xlate && !mbox->of_xlate) + mbox->fw_xlate = fw_mbox_index_xlate; + + scoped_guard(mutex, &con_mutex) + list_add_tail(&mbox->node, &mbox_cons); + + return 0; +} +EXPORT_SYMBOL_GPL(mbox_controller_register); + +/** + * mbox_controller_unregister - Unregister the mailbox controller + * @mbox: Pointer to the mailbox controller. + */ +void mbox_controller_unregister(struct mbox_controller *mbox) +{ + int i; + + if (!mbox) + return; + + scoped_guard(mutex, &con_mutex) { + list_del(&mbox->node); + + for (i = 0; i < mbox->num_chans; i++) + mbox_free_channel(&mbox->chans[i]); + + if (mbox->txdone_poll) + hrtimer_cancel(&mbox->poll_hrt); + } +} +EXPORT_SYMBOL_GPL(mbox_controller_unregister); + +static void __devm_mbox_controller_unregister(struct device *dev, void *res) +{ + struct mbox_controller **mbox = res; + + mbox_controller_unregister(*mbox); +} + +/** + * devm_mbox_controller_register() - managed mbox_controller_register() + * @dev: device owning the mailbox controller being registered + * @mbox: mailbox controller being registered + * + * This function adds a device-managed resource that will make sure that the + * mailbox controller, which is registered using mbox_controller_register() + * as part of this function, will be unregistered along with the rest of + * device-managed resources upon driver probe failure or driver removal. + * + * Returns 0 on success or a negative error code on failure. + */ +int devm_mbox_controller_register(struct device *dev, + struct mbox_controller *mbox) +{ + struct mbox_controller **ptr; + int err; + + ptr = devres_alloc(__devm_mbox_controller_unregister, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + err = mbox_controller_register(mbox); + if (err < 0) { + devres_free(ptr); + return err; + } + + devres_add(dev, ptr); + *ptr = mbox; + + return 0; +} +EXPORT_SYMBOL_GPL(devm_mbox_controller_register); + +#ifdef CONFIG_DEBUG_FS +static void *mbox_seq_start(struct seq_file *s, loff_t *pos) +{ + mutex_lock(&con_mutex); + return seq_list_start(&mbox_cons, *pos); +} + +static void *mbox_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + return seq_list_next(v, &mbox_cons, pos); +} + +static void mbox_seq_stop(struct seq_file *s, void *v) +{ + mutex_unlock(&con_mutex); +} + +static int mbox_seq_show(struct seq_file *seq, void *v) +{ + const struct mbox_controller *mbox = list_entry(v, struct mbox_controller, node); + + seq_printf(seq, "%s:\n", dev_name(mbox->dev)); + + for (unsigned int i = 0; i < mbox->num_chans; i++) { + struct mbox_chan *chan = &mbox->chans[i]; + + scoped_guard(spinlock_irqsave, &chan->lock) { + if (chan->cl) { + struct device *cl_dev = chan->cl->dev; + + seq_printf(seq, " %3u: %s\n", i, + cl_dev ? dev_name(cl_dev) : "NULL device"); + } + } + } + + return 0; +} + +static const struct seq_operations mbox_sops = { + .start = mbox_seq_start, + .next = mbox_seq_next, + .stop = mbox_seq_stop, + .show = mbox_seq_show, +}; +DEFINE_SEQ_ATTRIBUTE(mbox); + +/* + * subsys_initcall() is used here but controllers may already have been + * registered earlier or will be later. The rationale is that debugfs is + * accessed only late, i.e. from userspace. So, files created here must make no + * assumptions about initcall ordering. + */ +static int __init mbox_init(void) +{ + struct dentry *mbox_debugfs = debugfs_create_dir("mailbox", NULL); + + debugfs_create_file("mailbox_summary", 0444, mbox_debugfs, NULL, &mbox_fops); + return 0; +} +subsys_initcall(mbox_init); +#endif /* DEBUG_FS */ diff --git a/drivers/mailbox/mtk-adsp-mailbox.c b/drivers/mailbox/mtk-adsp-mailbox.c new file mode 100644 index 000000000..8bcecddee --- /dev/null +++ b/drivers/mailbox/mtk-adsp-mailbox.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 MediaTek Corporation. All rights reserved. + * Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> + */ + +#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> + +struct mtk_adsp_mbox_priv { + struct device *dev; + struct mbox_controller mbox; + void __iomem *va_mboxreg; + const struct mtk_adsp_mbox_cfg *cfg; + int irq; +}; + +struct mtk_adsp_mbox_cfg { + u32 set_in; + u32 set_out; + u32 clr_in; + u32 clr_out; +}; + +static inline struct mtk_adsp_mbox_priv *get_mtk_adsp_mbox_priv(struct mbox_controller *mbox) +{ + return container_of(mbox, struct mtk_adsp_mbox_priv, mbox); +} + +static irqreturn_t mtk_adsp_mbox_irq(int irq, void *data) +{ + struct mbox_chan *chan = data; + struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox); + u32 op = readl(priv->va_mboxreg + priv->cfg->set_out); + + writel(op, priv->va_mboxreg + priv->cfg->clr_out); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mtk_adsp_mbox_isr(int irq, void *data) +{ + struct mbox_chan *chan = data; + + mbox_chan_received_data(chan, NULL); + + return IRQ_HANDLED; +} + +static struct mbox_chan *mtk_adsp_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + return mbox->chans; +} + +static int mtk_adsp_mbox_startup(struct mbox_chan *chan) +{ + struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox); + + /* Clear ADSP mbox command */ + writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_in); + writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_out); + + enable_irq(priv->irq); + + return 0; +} + +static void mtk_adsp_mbox_shutdown(struct mbox_chan *chan) +{ + struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox); + + disable_irq(priv->irq); + + /* Clear ADSP mbox command */ + writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_in); + writel(0xFFFFFFFF, priv->va_mboxreg + priv->cfg->clr_out); +} + +static int mtk_adsp_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox); + u32 *msg = data; + + writel(*msg, priv->va_mboxreg + priv->cfg->set_in); + + return 0; +} + +static bool mtk_adsp_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct mtk_adsp_mbox_priv *priv = get_mtk_adsp_mbox_priv(chan->mbox); + + return readl(priv->va_mboxreg + priv->cfg->set_in) == 0; +} + +static const struct mbox_chan_ops mtk_adsp_mbox_chan_ops = { + .send_data = mtk_adsp_mbox_send_data, + .startup = mtk_adsp_mbox_startup, + .shutdown = mtk_adsp_mbox_shutdown, + .last_tx_done = mtk_adsp_mbox_last_tx_done, +}; + +static int mtk_adsp_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_adsp_mbox_priv *priv; + const struct mtk_adsp_mbox_cfg *cfg; + struct mbox_controller *mbox; + int ret, irq; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mbox = &priv->mbox; + mbox->dev = dev; + mbox->ops = &mtk_adsp_mbox_chan_ops; + mbox->txdone_irq = false; + mbox->txdone_poll = true; + mbox->of_xlate = mtk_adsp_mbox_xlate; + mbox->num_chans = 1; + mbox->chans = devm_kzalloc(dev, sizeof(*mbox->chans), GFP_KERNEL); + if (!mbox->chans) + return -ENOMEM; + + priv->va_mboxreg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->va_mboxreg)) + return PTR_ERR(priv->va_mboxreg); + + cfg = of_device_get_match_data(dev); + if (!cfg) + return -EINVAL; + priv->cfg = cfg; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + priv->irq = irq; + ret = devm_request_threaded_irq(dev, irq, mtk_adsp_mbox_irq, + mtk_adsp_mbox_isr, + IRQF_TRIGGER_NONE | IRQF_NO_AUTOEN, + dev_name(dev), mbox->chans); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return devm_mbox_controller_register(dev, &priv->mbox); +} + +static const struct mtk_adsp_mbox_cfg mt8186_adsp_mbox_cfg = { + .set_in = 0x00, + .set_out = 0x04, + .clr_in = 0x08, + .clr_out = 0x0C, +}; + +static const struct mtk_adsp_mbox_cfg mt8195_adsp_mbox_cfg = { + .set_in = 0x00, + .set_out = 0x1c, + .clr_in = 0x04, + .clr_out = 0x20, +}; + +static const struct of_device_id mtk_adsp_mbox_of_match[] = { + { .compatible = "mediatek,mt8186-adsp-mbox", .data = &mt8186_adsp_mbox_cfg }, + { .compatible = "mediatek,mt8195-adsp-mbox", .data = &mt8195_adsp_mbox_cfg }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_adsp_mbox_of_match); + +static struct platform_driver mtk_adsp_mbox_driver = { + .probe = mtk_adsp_mbox_probe, + .driver = { + .name = "mtk_adsp_mbox", + .of_match_table = mtk_adsp_mbox_of_match, + }, +}; +module_platform_driver(mtk_adsp_mbox_driver); + +MODULE_AUTHOR("Allen-KH Cheng <Allen-KH.Cheng@mediatek.com>"); +MODULE_DESCRIPTION("MTK ADSP Mailbox Controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c new file mode 100644 index 000000000..e523c84b4 --- /dev/null +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -0,0 +1,887 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2018 MediaTek Inc. + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/sizes.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/mtk-cmdq-mailbox.h> +#include <linux/of.h> + +#define CMDQ_MBOX_AUTOSUSPEND_DELAY_MS 100 + +#define CMDQ_OP_CODE_MASK (0xff << CMDQ_OP_CODE_SHIFT) +#define CMDQ_NUM_CMD(t) (t->cmd_buf_size / CMDQ_INST_SIZE) + +#define CMDQ_CURR_IRQ_STATUS 0x10 +#define CMDQ_SYNC_TOKEN_UPDATE 0x68 +#define CMDQ_THR_SLOT_CYCLES 0x30 +#define CMDQ_THR_BASE 0x100 +#define CMDQ_THR_SIZE 0x80 +#define CMDQ_THR_WARM_RESET 0x00 +#define CMDQ_THR_ENABLE_TASK 0x04 +#define CMDQ_THR_SUSPEND_TASK 0x08 +#define CMDQ_THR_CURR_STATUS 0x0c +#define CMDQ_THR_IRQ_STATUS 0x10 +#define CMDQ_THR_IRQ_ENABLE 0x14 +#define CMDQ_THR_CURR_ADDR 0x20 +#define CMDQ_THR_END_ADDR 0x24 +#define CMDQ_THR_WAIT_TOKEN 0x30 +#define CMDQ_THR_PRIORITY 0x40 + +#define GCE_GCTL_VALUE 0x48 +#define GCE_CTRL_BY_SW GENMASK(2, 0) +#define GCE_DDR_EN GENMASK(18, 16) + +#define GCE_VM_ID_MAP(n) (0x5018 + (n) / 10 * 4) +#define GCE_VM_ID_MAP_THR_FLD_SHIFT(n) ((n) % 10 * 3) +#define GCE_VM_ID_MAP_HOST_VM GENMASK(2, 0) +#define GCE_VM_CPR_GSIZE 0x50c4 +#define GCE_VM_CPR_GSIZE_FLD_SHIFT(vm_id) ((vm_id) * 4) +#define GCE_VM_CPR_GSIZE_MAX GENMASK(3, 0) + +#define CMDQ_THR_ACTIVE_SLOT_CYCLES 0x3200 +#define CMDQ_THR_ENABLED 0x1 +#define CMDQ_THR_DISABLED 0x0 +#define CMDQ_THR_SUSPEND 0x1 +#define CMDQ_THR_RESUME 0x0 +#define CMDQ_THR_STATUS_SUSPENDED BIT(1) +#define CMDQ_THR_DO_WARM_RESET BIT(0) +#define CMDQ_THR_IRQ_DONE 0x1 +#define CMDQ_THR_IRQ_ERROR 0x12 +#define CMDQ_THR_IRQ_EN (CMDQ_THR_IRQ_ERROR | CMDQ_THR_IRQ_DONE) +#define CMDQ_THR_IS_WAITING BIT(31) + +#define CMDQ_JUMP_BY_OFFSET 0x10000000 +#define CMDQ_JUMP_BY_PA 0x10000001 + +struct cmdq_thread { + struct mbox_chan *chan; + void __iomem *base; + struct list_head task_busy_list; + u32 priority; +}; + +struct cmdq_task { + struct cmdq *cmdq; + struct list_head list_entry; + dma_addr_t pa_base; + struct cmdq_thread *thread; + struct cmdq_pkt *pkt; /* the packet sent from mailbox client */ +}; + +struct cmdq { + struct mbox_controller mbox; + void __iomem *base; + int irq; + u32 irq_mask; + const struct gce_plat *pdata; + struct cmdq_thread *thread; + struct clk_bulk_data *clocks; + bool suspended; +}; + +struct gce_plat { + u32 thread_nr; + u8 shift; + dma_addr_t mminfra_offset; + bool control_by_sw; + bool sw_ddr_en; + bool gce_vm; + u32 gce_num; +}; + +static inline u32 cmdq_convert_gce_addr(dma_addr_t addr, const struct gce_plat *pdata) +{ + /* Convert DMA addr (PA or IOVA) to GCE readable addr */ + return (addr + pdata->mminfra_offset) >> pdata->shift; +} + +static inline dma_addr_t cmdq_revert_gce_addr(u32 addr, const struct gce_plat *pdata) +{ + /* Revert GCE readable addr to DMA addr (PA or IOVA) */ + return ((dma_addr_t)addr << pdata->shift) - pdata->mminfra_offset; +} + +void cmdq_get_mbox_priv(struct mbox_chan *chan, struct cmdq_mbox_priv *priv) +{ + struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); + + priv->shift_pa = cmdq->pdata->shift; + priv->mminfra_offset = cmdq->pdata->mminfra_offset; +} +EXPORT_SYMBOL(cmdq_get_mbox_priv); + +u8 cmdq_get_shift_pa(struct mbox_chan *chan) +{ + struct cmdq *cmdq = container_of(chan->mbox, struct cmdq, mbox); + + return cmdq->pdata->shift; +} +EXPORT_SYMBOL(cmdq_get_shift_pa); + +static void cmdq_vm_init(struct cmdq *cmdq) +{ + int i; + u32 vm_cpr_gsize = 0, vm_id_map = 0; + u32 *vm_map = NULL; + + if (!cmdq->pdata->gce_vm) + return; + + vm_map = kcalloc(cmdq->pdata->thread_nr, sizeof(*vm_map), GFP_KERNEL); + if (!vm_map) + return; + + /* only configure the max CPR SRAM size to host vm (vm_id = 0) currently */ + vm_cpr_gsize = GCE_VM_CPR_GSIZE_MAX << GCE_VM_CPR_GSIZE_FLD_SHIFT(0); + + /* set all thread mapping to host vm currently */ + for (i = 0; i < cmdq->pdata->thread_nr; i++) + vm_map[i] = GCE_VM_ID_MAP_HOST_VM << GCE_VM_ID_MAP_THR_FLD_SHIFT(i); + + /* set the amount of CPR SRAM to allocate to each VM */ + writel(vm_cpr_gsize, cmdq->base + GCE_VM_CPR_GSIZE); + + /* config CPR_GSIZE before setting VM_ID_MAP to avoid data leakage */ + for (i = 0; i < cmdq->pdata->thread_nr; i++) { + vm_id_map |= vm_map[i]; + /* config every 10 threads, e.g., thread id=0~9, 10~19, ..., into one register */ + if ((i + 1) % 10 == 0) { + writel(vm_id_map, cmdq->base + GCE_VM_ID_MAP(i)); + vm_id_map = 0; + } + } + /* config remaining threads settings */ + if (cmdq->pdata->thread_nr % 10 != 0) + writel(vm_id_map, cmdq->base + GCE_VM_ID_MAP(cmdq->pdata->thread_nr - 1)); + + kfree(vm_map); +} + +static void cmdq_gctl_value_toggle(struct cmdq *cmdq, bool ddr_enable) +{ + u32 val = cmdq->pdata->control_by_sw ? GCE_CTRL_BY_SW : 0; + + if (!cmdq->pdata->control_by_sw && !cmdq->pdata->sw_ddr_en) + return; + + if (cmdq->pdata->sw_ddr_en && ddr_enable) + val |= GCE_DDR_EN; + + writel(val, cmdq->base + GCE_GCTL_VALUE); +} + +static int cmdq_thread_suspend(struct cmdq *cmdq, struct cmdq_thread *thread) +{ + u32 status; + + writel(CMDQ_THR_SUSPEND, thread->base + CMDQ_THR_SUSPEND_TASK); + + /* If already disabled, treat as suspended successful. */ + if (!(readl(thread->base + CMDQ_THR_ENABLE_TASK) & CMDQ_THR_ENABLED)) + return 0; + + if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_CURR_STATUS, + status, status & CMDQ_THR_STATUS_SUSPENDED, 0, 10)) { + dev_err(cmdq->mbox.dev, "suspend GCE thread 0x%x failed\n", + (u32)(thread->base - cmdq->base)); + return -EFAULT; + } + + return 0; +} + +static void cmdq_thread_resume(struct cmdq_thread *thread) +{ + writel(CMDQ_THR_RESUME, thread->base + CMDQ_THR_SUSPEND_TASK); +} + +static void cmdq_init(struct cmdq *cmdq) +{ + int i; + + WARN_ON(clk_bulk_enable(cmdq->pdata->gce_num, cmdq->clocks)); + + cmdq_vm_init(cmdq); + cmdq_gctl_value_toggle(cmdq, true); + + writel(CMDQ_THR_ACTIVE_SLOT_CYCLES, cmdq->base + CMDQ_THR_SLOT_CYCLES); + for (i = 0; i <= CMDQ_MAX_EVENT; i++) + writel(i, cmdq->base + CMDQ_SYNC_TOKEN_UPDATE); + clk_bulk_disable(cmdq->pdata->gce_num, cmdq->clocks); +} + +static int cmdq_thread_reset(struct cmdq *cmdq, struct cmdq_thread *thread) +{ + u32 warm_reset; + + writel(CMDQ_THR_DO_WARM_RESET, thread->base + CMDQ_THR_WARM_RESET); + if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_WARM_RESET, + warm_reset, !(warm_reset & CMDQ_THR_DO_WARM_RESET), + 0, 10)) { + dev_err(cmdq->mbox.dev, "reset GCE thread 0x%x failed\n", + (u32)(thread->base - cmdq->base)); + return -EFAULT; + } + + return 0; +} + +static void cmdq_thread_disable(struct cmdq *cmdq, struct cmdq_thread *thread) +{ + cmdq_thread_reset(cmdq, thread); + writel(CMDQ_THR_DISABLED, thread->base + CMDQ_THR_ENABLE_TASK); +} + +/* notify GCE to re-fetch commands by setting GCE thread PC */ +static void cmdq_thread_invalidate_fetched_data(struct cmdq_thread *thread) +{ + writel(readl(thread->base + CMDQ_THR_CURR_ADDR), + thread->base + CMDQ_THR_CURR_ADDR); +} + +static void cmdq_task_insert_into_thread(struct cmdq_task *task) +{ + struct device *dev = task->cmdq->mbox.dev; + struct cmdq_thread *thread = task->thread; + struct cmdq_task *prev_task = list_last_entry( + &thread->task_busy_list, typeof(*task), list_entry); + u64 *prev_task_base = prev_task->pkt->va_base; + u32 gce_addr = cmdq_convert_gce_addr(task->pa_base, task->cmdq->pdata); + + /* let previous task jump to this task */ + dma_sync_single_for_cpu(dev, prev_task->pa_base, + prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE); + prev_task_base[CMDQ_NUM_CMD(prev_task->pkt) - 1] = (u64)CMDQ_JUMP_BY_PA << 32 | gce_addr; + dma_sync_single_for_device(dev, prev_task->pa_base, + prev_task->pkt->cmd_buf_size, DMA_TO_DEVICE); + + cmdq_thread_invalidate_fetched_data(thread); +} + +static bool cmdq_thread_is_in_wfe(struct cmdq_thread *thread) +{ + return readl(thread->base + CMDQ_THR_WAIT_TOKEN) & CMDQ_THR_IS_WAITING; +} + +static void cmdq_task_exec_done(struct cmdq_task *task, int sta) +{ + struct cmdq_cb_data data; + + data.sta = sta; + data.pkt = task->pkt; + mbox_chan_received_data(task->thread->chan, &data); + + list_del(&task->list_entry); +} + +static void cmdq_task_handle_error(struct cmdq_task *task) +{ + struct cmdq_thread *thread = task->thread; + struct cmdq_task *next_task; + struct cmdq *cmdq = task->cmdq; + + dev_err(cmdq->mbox.dev, "task 0x%p error\n", task); + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + next_task = list_first_entry_or_null(&thread->task_busy_list, + struct cmdq_task, list_entry); + if (next_task) + writel(next_task->pa_base >> cmdq->pdata->shift, + thread->base + CMDQ_THR_CURR_ADDR); + cmdq_thread_resume(thread); +} + +static void cmdq_thread_irq_handler(struct cmdq *cmdq, + struct cmdq_thread *thread) +{ + struct cmdq_task *task, *tmp, *curr_task = NULL; + u32 irq_flag, gce_addr; + dma_addr_t curr_pa, task_end_pa; + bool err; + + irq_flag = readl(thread->base + CMDQ_THR_IRQ_STATUS); + writel(~irq_flag, thread->base + CMDQ_THR_IRQ_STATUS); + + /* + * When ISR call this function, another CPU core could run + * "release task" right before we acquire the spin lock, and thus + * reset / disable this GCE thread, so we need to check the enable + * bit of this GCE thread. + */ + if (!(readl(thread->base + CMDQ_THR_ENABLE_TASK) & CMDQ_THR_ENABLED)) + return; + + if (irq_flag & CMDQ_THR_IRQ_ERROR) + err = true; + else if (irq_flag & CMDQ_THR_IRQ_DONE) + err = false; + else + return; + + gce_addr = readl(thread->base + CMDQ_THR_CURR_ADDR); + curr_pa = cmdq_revert_gce_addr(gce_addr, cmdq->pdata); + + list_for_each_entry_safe(task, tmp, &thread->task_busy_list, + list_entry) { + task_end_pa = task->pa_base + task->pkt->cmd_buf_size; + if (curr_pa >= task->pa_base && curr_pa < task_end_pa) + curr_task = task; + + if (!curr_task || curr_pa == task_end_pa - CMDQ_INST_SIZE) { + cmdq_task_exec_done(task, 0); + kfree(task); + } else if (err) { + cmdq_task_exec_done(task, -ENOEXEC); + cmdq_task_handle_error(curr_task); + kfree(task); + } + + if (curr_task) + break; + } + + if (list_empty(&thread->task_busy_list)) + cmdq_thread_disable(cmdq, thread); +} + +static irqreturn_t cmdq_irq_handler(int irq, void *dev) +{ + struct cmdq *cmdq = dev; + unsigned long irq_status, flags = 0L; + int bit; + + irq_status = readl(cmdq->base + CMDQ_CURR_IRQ_STATUS) & cmdq->irq_mask; + if (!(irq_status ^ cmdq->irq_mask)) + return IRQ_NONE; + + for_each_clear_bit(bit, &irq_status, cmdq->pdata->thread_nr) { + struct cmdq_thread *thread = &cmdq->thread[bit]; + + spin_lock_irqsave(&thread->chan->lock, flags); + cmdq_thread_irq_handler(cmdq, thread); + spin_unlock_irqrestore(&thread->chan->lock, flags); + } + + pm_runtime_mark_last_busy(cmdq->mbox.dev); + + return IRQ_HANDLED; +} + +static int cmdq_runtime_resume(struct device *dev) +{ + struct cmdq *cmdq = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_enable(cmdq->pdata->gce_num, cmdq->clocks); + if (ret) + return ret; + + cmdq_gctl_value_toggle(cmdq, true); + return 0; +} + +static int cmdq_runtime_suspend(struct device *dev) +{ + struct cmdq *cmdq = dev_get_drvdata(dev); + + cmdq_gctl_value_toggle(cmdq, false); + clk_bulk_disable(cmdq->pdata->gce_num, cmdq->clocks); + return 0; +} + +static int cmdq_suspend(struct device *dev) +{ + struct cmdq *cmdq = dev_get_drvdata(dev); + struct cmdq_thread *thread; + int i; + bool task_running = false; + + cmdq->suspended = true; + + for (i = 0; i < cmdq->pdata->thread_nr; i++) { + thread = &cmdq->thread[i]; + if (!list_empty(&thread->task_busy_list)) { + task_running = true; + break; + } + } + + if (task_running) + dev_warn(dev, "exist running task(s) in suspend\n"); + + return pm_runtime_force_suspend(dev); +} + +static int cmdq_resume(struct device *dev) +{ + struct cmdq *cmdq = dev_get_drvdata(dev); + + WARN_ON(pm_runtime_force_resume(dev)); + cmdq->suspended = false; + + return 0; +} + +static void cmdq_remove(struct platform_device *pdev) +{ + struct cmdq *cmdq = platform_get_drvdata(pdev); + + if (!IS_ENABLED(CONFIG_PM)) + cmdq_runtime_suspend(&pdev->dev); + + clk_bulk_unprepare(cmdq->pdata->gce_num, cmdq->clocks); +} + +static int cmdq_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct cmdq_pkt *pkt = (struct cmdq_pkt *)data; + struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv; + struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); + struct cmdq_task *task; + u32 gce_addr; + dma_addr_t curr_pa, end_pa; + + /* Client should not flush new tasks if suspended. */ + WARN_ON(cmdq->suspended); + + task = kzalloc_obj(*task, GFP_ATOMIC); + if (!task) + return -ENOMEM; + + task->cmdq = cmdq; + INIT_LIST_HEAD(&task->list_entry); + task->pa_base = pkt->pa_base; + task->thread = thread; + task->pkt = pkt; + + if (list_empty(&thread->task_busy_list)) { + /* + * The thread reset will clear thread related register to 0, + * including pc, end, priority, irq, suspend and enable. Thus + * set CMDQ_THR_ENABLED to CMDQ_THR_ENABLE_TASK will enable + * thread and make it running. + */ + WARN_ON(cmdq_thread_reset(cmdq, thread) < 0); + + gce_addr = cmdq_convert_gce_addr(task->pa_base, cmdq->pdata); + writel(gce_addr, thread->base + CMDQ_THR_CURR_ADDR); + gce_addr = cmdq_convert_gce_addr(task->pa_base + pkt->cmd_buf_size, cmdq->pdata); + writel(gce_addr, thread->base + CMDQ_THR_END_ADDR); + + writel(thread->priority, thread->base + CMDQ_THR_PRIORITY); + writel(CMDQ_THR_IRQ_EN, thread->base + CMDQ_THR_IRQ_ENABLE); + writel(CMDQ_THR_ENABLED, thread->base + CMDQ_THR_ENABLE_TASK); + } else { + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + gce_addr = readl(thread->base + CMDQ_THR_CURR_ADDR); + curr_pa = cmdq_revert_gce_addr(gce_addr, cmdq->pdata); + gce_addr = readl(thread->base + CMDQ_THR_END_ADDR); + end_pa = cmdq_revert_gce_addr(gce_addr, cmdq->pdata); + /* check boundary */ + if (curr_pa == end_pa - CMDQ_INST_SIZE || + curr_pa == end_pa) { + /* set to this task directly */ + gce_addr = cmdq_convert_gce_addr(task->pa_base, cmdq->pdata); + writel(gce_addr, thread->base + CMDQ_THR_CURR_ADDR); + } else { + cmdq_task_insert_into_thread(task); + smp_mb(); /* modify jump before enable thread */ + } + gce_addr = cmdq_convert_gce_addr(task->pa_base + pkt->cmd_buf_size, cmdq->pdata); + writel(gce_addr, thread->base + CMDQ_THR_END_ADDR); + cmdq_thread_resume(thread); + } + list_move_tail(&task->list_entry, &thread->task_busy_list); + + return 0; +} + +static int cmdq_mbox_startup(struct mbox_chan *chan) +{ + return 0; +} + +static void cmdq_mbox_shutdown(struct mbox_chan *chan) +{ + struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv; + struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); + struct cmdq_task *task, *tmp; + unsigned long flags; + + WARN_ON(pm_runtime_get_sync(cmdq->mbox.dev) < 0); + + spin_lock_irqsave(&thread->chan->lock, flags); + if (list_empty(&thread->task_busy_list)) + goto done; + + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + + /* make sure executed tasks have success callback */ + cmdq_thread_irq_handler(cmdq, thread); + if (list_empty(&thread->task_busy_list)) + goto done; + + list_for_each_entry_safe(task, tmp, &thread->task_busy_list, + list_entry) { + cmdq_task_exec_done(task, -ECONNABORTED); + kfree(task); + } + + cmdq_thread_disable(cmdq, thread); + +done: + /* + * The thread->task_busy_list empty means thread already disable. The + * cmdq_mbox_send_data() always reset thread which clear disable and + * suspend statue when first pkt send to channel, so there is no need + * to do any operation here, only unlock and leave. + */ + spin_unlock_irqrestore(&thread->chan->lock, flags); + + pm_runtime_mark_last_busy(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); +} + +static int cmdq_mbox_flush(struct mbox_chan *chan, unsigned long timeout) +{ + struct cmdq_thread *thread = (struct cmdq_thread *)chan->con_priv; + struct cmdq_cb_data data; + struct cmdq *cmdq = dev_get_drvdata(chan->mbox->dev); + struct cmdq_task *task, *tmp; + unsigned long flags; + u32 enable; + int ret; + + ret = pm_runtime_get_sync(cmdq->mbox.dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&thread->chan->lock, flags); + if (list_empty(&thread->task_busy_list)) + goto out; + + WARN_ON(cmdq_thread_suspend(cmdq, thread) < 0); + if (!cmdq_thread_is_in_wfe(thread)) + goto wait; + + list_for_each_entry_safe(task, tmp, &thread->task_busy_list, + list_entry) { + data.sta = -ECONNABORTED; + data.pkt = task->pkt; + mbox_chan_received_data(task->thread->chan, &data); + list_del(&task->list_entry); + kfree(task); + } + + cmdq_thread_resume(thread); + cmdq_thread_disable(cmdq, thread); + +out: + spin_unlock_irqrestore(&thread->chan->lock, flags); + pm_runtime_mark_last_busy(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); + + return 0; + +wait: + cmdq_thread_resume(thread); + spin_unlock_irqrestore(&thread->chan->lock, flags); + if (readl_poll_timeout_atomic(thread->base + CMDQ_THR_ENABLE_TASK, + enable, enable == 0, 1, timeout)) { + dev_err(cmdq->mbox.dev, "Fail to wait GCE thread 0x%x done\n", + (u32)(thread->base - cmdq->base)); + + return -EFAULT; + } + pm_runtime_mark_last_busy(cmdq->mbox.dev); + pm_runtime_put_autosuspend(cmdq->mbox.dev); + return 0; +} + +static const struct mbox_chan_ops cmdq_mbox_chan_ops = { + .send_data = cmdq_mbox_send_data, + .startup = cmdq_mbox_startup, + .shutdown = cmdq_mbox_shutdown, + .flush = cmdq_mbox_flush, +}; + +static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + int ind = sp->args[0]; + struct cmdq_thread *thread; + + if (ind >= mbox->num_chans) + return ERR_PTR(-EINVAL); + + thread = (struct cmdq_thread *)mbox->chans[ind].con_priv; + thread->priority = sp->args[1]; + thread->chan = &mbox->chans[ind]; + + return &mbox->chans[ind]; +} + +static int cmdq_get_clocks(struct device *dev, struct cmdq *cmdq) +{ + static const char * const gce_name = "gce"; + struct device_node *parent = dev->of_node->parent; + struct clk_bulk_data *clks; + + cmdq->clocks = devm_kcalloc(dev, cmdq->pdata->gce_num, + sizeof(*cmdq->clocks), GFP_KERNEL); + if (!cmdq->clocks) + return -ENOMEM; + + if (cmdq->pdata->gce_num == 1) { + clks = &cmdq->clocks[0]; + + clks->id = gce_name; + clks->clk = devm_clk_get(dev, NULL); + if (IS_ERR(clks->clk)) + return dev_err_probe(dev, PTR_ERR(clks->clk), + "failed to get gce clock\n"); + + return 0; + } + + /* + * If there is more than one GCE, get the clocks for the others too, + * as the clock of the main GCE must be enabled for additional IPs + * to be reachable. + */ + for_each_child_of_node_scoped(parent, node) { + int alias_id = of_alias_get_id(node, gce_name); + + if (alias_id < 0 || alias_id >= cmdq->pdata->gce_num) + continue; + + clks = &cmdq->clocks[alias_id]; + + clks->id = devm_kasprintf(dev, GFP_KERNEL, "gce%d", alias_id); + if (!clks->id) + return -ENOMEM; + + clks->clk = of_clk_get(node, 0); + if (IS_ERR(clks->clk)) + return dev_err_probe(dev, PTR_ERR(clks->clk), + "failed to get gce%d clock\n", alias_id); + } + + return 0; +} + +static int cmdq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cmdq *cmdq; + int err, i; + + cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL); + if (!cmdq) + return -ENOMEM; + + cmdq->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(cmdq->base)) + return PTR_ERR(cmdq->base); + + cmdq->irq = platform_get_irq(pdev, 0); + if (cmdq->irq < 0) + return cmdq->irq; + + cmdq->pdata = device_get_match_data(dev); + if (!cmdq->pdata) { + dev_err(dev, "failed to get match data\n"); + return -EINVAL; + } + + cmdq->irq_mask = GENMASK(cmdq->pdata->thread_nr - 1, 0); + + dev_dbg(dev, "cmdq device: addr:0x%p, va:0x%p, irq:%d\n", + dev, cmdq->base, cmdq->irq); + + err = cmdq_get_clocks(dev, cmdq); + if (err) + return err; + + dma_set_coherent_mask(dev, + DMA_BIT_MASK(sizeof(u32) * BITS_PER_BYTE + cmdq->pdata->shift)); + + cmdq->mbox.dev = dev; + cmdq->mbox.chans = devm_kcalloc(dev, cmdq->pdata->thread_nr, + sizeof(*cmdq->mbox.chans), GFP_KERNEL); + if (!cmdq->mbox.chans) + return -ENOMEM; + + cmdq->mbox.num_chans = cmdq->pdata->thread_nr; + cmdq->mbox.ops = &cmdq_mbox_chan_ops; + cmdq->mbox.of_xlate = cmdq_xlate; + + /* make use of MBOX_TXDONE_BY_ACK */ + cmdq->mbox.txdone_irq = false; + cmdq->mbox.txdone_poll = false; + + cmdq->thread = devm_kcalloc(dev, cmdq->pdata->thread_nr, + sizeof(*cmdq->thread), GFP_KERNEL); + if (!cmdq->thread) + return -ENOMEM; + + for (i = 0; i < cmdq->pdata->thread_nr; i++) { + cmdq->thread[i].base = cmdq->base + CMDQ_THR_BASE + + CMDQ_THR_SIZE * i; + INIT_LIST_HEAD(&cmdq->thread[i].task_busy_list); + cmdq->mbox.chans[i].con_priv = (void *)&cmdq->thread[i]; + } + + platform_set_drvdata(pdev, cmdq); + + WARN_ON(clk_bulk_prepare(cmdq->pdata->gce_num, cmdq->clocks)); + + cmdq_init(cmdq); + + err = devm_request_irq(dev, cmdq->irq, cmdq_irq_handler, IRQF_SHARED, + "mtk_cmdq", cmdq); + if (err < 0) { + dev_err(dev, "failed to register ISR (%d)\n", err); + return err; + } + + /* If Runtime PM is not available enable the clocks now. */ + if (!IS_ENABLED(CONFIG_PM)) { + err = cmdq_runtime_resume(dev); + if (err) + return err; + } + + err = devm_pm_runtime_enable(dev); + if (err) + return err; + + pm_runtime_set_autosuspend_delay(dev, CMDQ_MBOX_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + err = devm_mbox_controller_register(dev, &cmdq->mbox); + if (err < 0) { + dev_err(dev, "failed to register mailbox: %d\n", err); + return err; + } + + return 0; +} + +static const struct dev_pm_ops cmdq_pm_ops = { + .suspend = cmdq_suspend, + .resume = cmdq_resume, + SET_RUNTIME_PM_OPS(cmdq_runtime_suspend, + cmdq_runtime_resume, NULL) +}; + +static const struct gce_plat gce_plat_mt6779 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_mt8173 = { + .thread_nr = 16, + .shift = 0, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_mt8183 = { + .thread_nr = 24, + .shift = 0, + .control_by_sw = false, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_mt8186 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = true, + .sw_ddr_en = true, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_mt8188 = { + .thread_nr = 32, + .shift = 3, + .control_by_sw = true, + .gce_num = 2 +}; + +static const struct gce_plat gce_plat_mt8192 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = true, + .gce_num = 1 +}; + +static const struct gce_plat gce_plat_mt8195 = { + .thread_nr = 24, + .shift = 3, + .control_by_sw = true, + .gce_num = 2 +}; + +static const struct gce_plat gce_plat_mt8196 = { + .thread_nr = 32, + .shift = 3, + .mminfra_offset = SZ_2G, + .control_by_sw = true, + .sw_ddr_en = true, + .gce_vm = true, + .gce_num = 2 +}; + +static const struct of_device_id cmdq_of_ids[] = { + {.compatible = "mediatek,mt6779-gce", .data = (void *)&gce_plat_mt6779}, + {.compatible = "mediatek,mt8173-gce", .data = (void *)&gce_plat_mt8173}, + {.compatible = "mediatek,mt8183-gce", .data = (void *)&gce_plat_mt8183}, + {.compatible = "mediatek,mt8186-gce", .data = (void *)&gce_plat_mt8186}, + {.compatible = "mediatek,mt8188-gce", .data = (void *)&gce_plat_mt8188}, + {.compatible = "mediatek,mt8192-gce", .data = (void *)&gce_plat_mt8192}, + {.compatible = "mediatek,mt8195-gce", .data = (void *)&gce_plat_mt8195}, + {.compatible = "mediatek,mt8196-gce", .data = (void *)&gce_plat_mt8196}, + {} +}; +MODULE_DEVICE_TABLE(of, cmdq_of_ids); + +static struct platform_driver cmdq_drv = { + .probe = cmdq_probe, + .remove = cmdq_remove, + .driver = { + .name = "mtk_cmdq", + .pm = &cmdq_pm_ops, + .of_match_table = cmdq_of_ids, + } +}; + +static int __init cmdq_drv_init(void) +{ + return platform_driver_register(&cmdq_drv); +} + +static void __exit cmdq_drv_exit(void) +{ + platform_driver_unregister(&cmdq_drv); +} + +subsys_initcall(cmdq_drv_init); +module_exit(cmdq_drv_exit); + +MODULE_DESCRIPTION("Mediatek Command Queue(CMDQ) Mailbox driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/mtk-gpueb-mailbox.c b/drivers/mailbox/mtk-gpueb-mailbox.c new file mode 100644 index 000000000..f6d2beccd --- /dev/null +++ b/drivers/mailbox/mtk-gpueb-mailbox.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek GPUEB mailbox driver for SoCs such as the MT8196 + * + * Copyright (C) 2025, Collabora Ltd. + * + * Developers harmed in the making of this driver: + * - Nicolas Frattaroli <nicolas.frattaroli@collabora.com> + */ + +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define GPUEB_MBOX_CTL_TX_STS 0x00 +#define GPUEB_MBOX_CTL_IRQ_SET 0x04 +#define GPUEB_MBOX_CTL_IRQ_CLR 0x74 +#define GPUEB_MBOX_CTL_RX_STS 0x78 + +#define GPUEB_MBOX_FULL BIT(0) /* i.e. we've received data */ +#define GPUEB_MBOX_BLOCKED BIT(1) /* i.e. the channel is shutdown */ + +#define GPUEB_MBOX_MAX_RX_SIZE 32 /* in bytes */ + +struct mtk_gpueb_mbox { + struct device *dev; + struct clk *clk; + void __iomem *mbox_mmio; + void __iomem *mbox_ctl; + struct mbox_controller mbox; + struct mtk_gpueb_mbox_chan *ch; + int irq; + const struct mtk_gpueb_mbox_variant *v; +}; + +/** + * struct mtk_gpueb_mbox_chan - per-channel runtime data + * @ebm: pointer to the parent &struct mtk_gpueb_mbox mailbox + * @full_name: descriptive name of channel for IRQ subsystem + * @num: channel number, starting at 0 + * @rx_status: signifies whether channel reception is turned off, or full + * @c: pointer to the constant &struct mtk_gpueb_mbox_chan_desc channel data + */ +struct mtk_gpueb_mbox_chan { + struct mtk_gpueb_mbox *ebm; + char *full_name; + u8 num; + atomic_t rx_status; + const struct mtk_gpueb_mbox_chan_desc *c; +}; + +/** + * struct mtk_gpueb_mbox_chan_desc - per-channel constant data + * @name: name of this channel + * @num: index of this channel, starting at 0 + * @tx_offset: byte offset measured from mmio base for outgoing data + * @tx_len: size, in bytes, of the outgoing data on this channel + * @rx_offset: bytes offset measured from mmio base for incoming data + * @rx_len: size, in bytes, of the incoming data on this channel + */ +struct mtk_gpueb_mbox_chan_desc { + const char *name; + const u8 num; + const u16 tx_offset; + const u8 tx_len; + const u16 rx_offset; + const u8 rx_len; +}; + +struct mtk_gpueb_mbox_variant { + const u8 num_channels; + const struct mtk_gpueb_mbox_chan_desc channels[] __counted_by(num_channels); +}; + +/** + * mtk_gpueb_mbox_read_rx - read RX buffer from MMIO into channel's RX buffer + * @buf: buffer to read into + * @chan: pointer to the channel to read + */ +static void mtk_gpueb_mbox_read_rx(void *buf, struct mtk_gpueb_mbox_chan *chan) +{ + memcpy_fromio(buf, chan->ebm->mbox_mmio + chan->c->rx_offset, chan->c->rx_len); +} + +static irqreturn_t mtk_gpueb_mbox_isr(int irq, void *data) +{ + struct mtk_gpueb_mbox_chan *ch = data; + u32 rx_sts; + + rx_sts = readl(ch->ebm->mbox_ctl + GPUEB_MBOX_CTL_RX_STS); + + if (rx_sts & BIT(ch->num)) { + if (!atomic_cmpxchg(&ch->rx_status, 0, GPUEB_MBOX_FULL | GPUEB_MBOX_BLOCKED)) + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t mtk_gpueb_mbox_thread(int irq, void *data) +{ + struct mtk_gpueb_mbox_chan *ch = data; + int status; + + status = atomic_cmpxchg(&ch->rx_status, GPUEB_MBOX_FULL | GPUEB_MBOX_BLOCKED, + GPUEB_MBOX_FULL); + if (status == (GPUEB_MBOX_FULL | GPUEB_MBOX_BLOCKED)) { + u8 buf[GPUEB_MBOX_MAX_RX_SIZE] = {}; + + mtk_gpueb_mbox_read_rx(buf, ch); + writel(BIT(ch->num), ch->ebm->mbox_ctl + GPUEB_MBOX_CTL_IRQ_CLR); + mbox_chan_received_data(&ch->ebm->mbox.chans[ch->num], buf); + atomic_set(&ch->rx_status, 0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int mtk_gpueb_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct mtk_gpueb_mbox_chan *ch = chan->con_priv; + u32 *values = data; + int i; + + if (atomic_read(&ch->rx_status)) + return -EBUSY; + + /* + * We don't want any fancy nonsense, just write the 32-bit values in + * order. memcpy_toio/__iowrite32_copy don't work here, as they may use + * writes of different sizes or memory ordering characteristics depending + * on the architecture, alignment and the current phase of the moon. + */ + for (i = 0; i < ch->c->tx_len; i += 4) + writel(values[i / 4], ch->ebm->mbox_mmio + ch->c->tx_offset + i); + + writel(BIT(ch->num), ch->ebm->mbox_ctl + GPUEB_MBOX_CTL_IRQ_SET); + + return 0; +} + +static int mtk_gpueb_mbox_startup(struct mbox_chan *chan) +{ + struct mtk_gpueb_mbox_chan *ch = chan->con_priv; + int ret; + + atomic_set(&ch->rx_status, 0); + + ret = clk_enable(ch->ebm->clk); + if (ret) { + dev_err(ch->ebm->dev, "Failed to enable EB clock: %pe\n", + ERR_PTR(ret)); + goto err_block; + } + + writel(BIT(ch->num), ch->ebm->mbox_ctl + GPUEB_MBOX_CTL_IRQ_CLR); + + ret = devm_request_threaded_irq(ch->ebm->dev, ch->ebm->irq, mtk_gpueb_mbox_isr, + mtk_gpueb_mbox_thread, IRQF_SHARED | IRQF_ONESHOT, + ch->full_name, ch); + if (ret) { + dev_err(ch->ebm->dev, "Failed to request IRQ: %pe\n", + ERR_PTR(ret)); + goto err_unclk; + } + + return 0; + +err_unclk: + clk_disable(ch->ebm->clk); +err_block: + atomic_set(&ch->rx_status, GPUEB_MBOX_BLOCKED); + + return ret; +} + +static void mtk_gpueb_mbox_shutdown(struct mbox_chan *chan) +{ + struct mtk_gpueb_mbox_chan *ch = chan->con_priv; + + atomic_set(&ch->rx_status, GPUEB_MBOX_BLOCKED); + + devm_free_irq(ch->ebm->dev, ch->ebm->irq, ch); + + clk_disable(ch->ebm->clk); +} + +static bool mtk_gpueb_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct mtk_gpueb_mbox_chan *ch = chan->con_priv; + + return !(readl(ch->ebm->mbox_ctl + GPUEB_MBOX_CTL_TX_STS) & BIT(ch->num)); +} + +static const struct mbox_chan_ops mtk_gpueb_mbox_ops = { + .send_data = mtk_gpueb_mbox_send_data, + .startup = mtk_gpueb_mbox_startup, + .shutdown = mtk_gpueb_mbox_shutdown, + .last_tx_done = mtk_gpueb_mbox_last_tx_done, +}; + +static int mtk_gpueb_mbox_probe(struct platform_device *pdev) +{ + struct mtk_gpueb_mbox_chan *ch; + struct mtk_gpueb_mbox *ebm; + unsigned int i; + + ebm = devm_kzalloc(&pdev->dev, sizeof(*ebm), GFP_KERNEL); + if (!ebm) + return -ENOMEM; + + ebm->dev = &pdev->dev; + ebm->v = of_device_get_match_data(ebm->dev); + + ebm->irq = platform_get_irq(pdev, 0); + if (ebm->irq < 0) + return ebm->irq; + + ebm->clk = devm_clk_get_prepared(ebm->dev, NULL); + if (IS_ERR(ebm->clk)) + return dev_err_probe(ebm->dev, PTR_ERR(ebm->clk), + "Failed to get 'eb' clock\n"); + + ebm->mbox_mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ebm->mbox_mmio)) + return dev_err_probe(ebm->dev, PTR_ERR(ebm->mbox_mmio), + "Couldn't map mailbox data registers\n"); + + ebm->mbox_ctl = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(ebm->mbox_ctl)) + return dev_err_probe( + ebm->dev, PTR_ERR(ebm->mbox_ctl), + "Couldn't map mailbox control registers\n"); + + ebm->ch = devm_kmalloc_array(ebm->dev, ebm->v->num_channels, + sizeof(*ebm->ch), GFP_KERNEL); + if (!ebm->ch) + return -ENOMEM; + + ebm->mbox.chans = devm_kcalloc(ebm->dev, ebm->v->num_channels, + sizeof(struct mbox_chan), GFP_KERNEL); + if (!ebm->mbox.chans) + return -ENOMEM; + + for (i = 0; i < ebm->v->num_channels; i++) { + ch = &ebm->ch[i]; + ch->c = &ebm->v->channels[i]; + if (ch->c->rx_len > GPUEB_MBOX_MAX_RX_SIZE) { + dev_err(ebm->dev, "Channel %s RX size (%d) too large\n", + ch->c->name, ch->c->rx_len); + return -EINVAL; + } + ch->full_name = devm_kasprintf(ebm->dev, GFP_KERNEL, "%s:%s", + dev_name(ebm->dev), ch->c->name); + if (!ch->full_name) + return -ENOMEM; + + ch->ebm = ebm; + ch->num = i; + spin_lock_init(&ebm->mbox.chans[i].lock); + ebm->mbox.chans[i].con_priv = ch; + atomic_set(&ch->rx_status, GPUEB_MBOX_BLOCKED); + } + + ebm->mbox.dev = ebm->dev; + ebm->mbox.num_chans = ebm->v->num_channels; + ebm->mbox.txdone_poll = true; + ebm->mbox.txpoll_period = 0; /* minimum hrtimer interval */ + ebm->mbox.ops = &mtk_gpueb_mbox_ops; + + dev_set_drvdata(ebm->dev, ebm); + + return devm_mbox_controller_register(ebm->dev, &ebm->mbox); +} + +static const struct mtk_gpueb_mbox_variant mtk_gpueb_mbox_mt8196 = { + .num_channels = 12, + .channels = { + { "fast-dvfs-event", 0, 0x0000, 16, 0x00e0, 16 }, + { "gpufreq", 1, 0x0010, 32, 0x00f0, 32 }, + { "sleep", 2, 0x0030, 12, 0x0110, 4 }, + { "timer", 3, 0x003c, 24, 0x0114, 4 }, + { "fhctl", 4, 0x0054, 36, 0x0118, 4 }, + { "ccf", 5, 0x0078, 16, 0x011c, 16 }, + { "gpumpu", 6, 0x0088, 24, 0x012c, 4 }, + { "fast-dvfs", 7, 0x00a0, 24, 0x0130, 24 }, + { "ipir-c-met", 8, 0x00b8, 4, 0x0148, 16 }, + { "ipis-c-met", 9, 0x00bc, 16, 0x0158, 4 }, + { "brisket", 10, 0x00cc, 16, 0x015c, 16 }, + { "ppb", 11, 0x00dc, 4, 0x016c, 4 }, + }, +}; + +static const struct of_device_id mtk_gpueb_mbox_of_ids[] = { + { .compatible = "mediatek,mt8196-gpueb-mbox", .data = &mtk_gpueb_mbox_mt8196 }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mtk_gpueb_mbox_of_ids); + +static struct platform_driver mtk_gpueb_mbox_drv = { + .probe = mtk_gpueb_mbox_probe, + .driver = { + .name = "mtk-gpueb-mbox", + .of_match_table = mtk_gpueb_mbox_of_ids, + } +}; +module_platform_driver(mtk_gpueb_mbox_drv); + +MODULE_AUTHOR("Nicolas Frattaroli <nicolas.frattaroli@collabora.com>"); +MODULE_DESCRIPTION("MediaTek GPUEB mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/mtk-vcp-mailbox.c b/drivers/mailbox/mtk-vcp-mailbox.c new file mode 100644 index 000000000..1b291b8ea --- /dev/null +++ b/drivers/mailbox/mtk-vcp-mailbox.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 MediaTek Corporation. All rights reserved. + * Author: Jjian Zhou <jjian.zhou.@mediatek.com> + */ + +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/mtk-vcp-mailbox.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct mtk_vcp_mbox { + struct mbox_controller mbox; + void __iomem *base; + struct device *dev; + const struct mtk_vcp_mbox_cfg *cfg; + struct mtk_ipi_info ipi_recv; + struct mbox_chan chans; +}; + +struct mtk_vcp_mbox_cfg { + u16 set_in; + u16 clr_out; +}; + +static irqreturn_t mtk_vcp_mbox_irq_thread(int irq, void *data) +{ + struct mtk_vcp_mbox *priv = data; + + /* get irq status */ + priv->ipi_recv.irq_status = readl(priv->base + priv->cfg->clr_out); + + __ioread32_copy(priv->ipi_recv.msg, priv->base, + MTK_VCP_MBOX_SLOT_MAX_SIZE / 4); + + mbox_chan_received_data(&priv->chans, &priv->ipi_recv); + + /* clear irq status */ + writel(priv->ipi_recv.irq_status, priv->base + priv->cfg->clr_out); + + return IRQ_HANDLED; +} + +static struct mbox_chan *mtk_vcp_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + if (sp->args_count) + return ERR_PTR(-EINVAL); + + return &mbox->chans[0]; +} + +static int mtk_vcp_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct mtk_vcp_mbox *priv = chan->con_priv; + struct mtk_ipi_info *ipi_info = data; + u32 status; + + if (!ipi_info->msg) { + dev_err(priv->dev, "msg buffer is NULL.\n"); + return -EINVAL; + } + + status = readl(priv->base + priv->cfg->set_in); + if (status & BIT(ipi_info->index)) { + dev_warn(priv->dev, "mailbox IPI %d is busy.\n", ipi_info->id); + return -EBUSY; + } + + if (ipi_info->slot_ofs + ipi_info->len > MTK_VCP_MBOX_SLOT_MAX_SIZE) + return -EINVAL; + __iowrite32_copy(priv->base + ipi_info->slot_ofs, ipi_info->msg, + ipi_info->len); + + writel(BIT(ipi_info->index), priv->base + priv->cfg->set_in); + + return 0; +} + +static bool mtk_vcp_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct mtk_ipi_info *ipi_info = chan->active_req; + struct mtk_vcp_mbox *priv = chan->con_priv; + + return !(readl(priv->base + priv->cfg->set_in) & BIT(ipi_info->index)); +} + +static const struct mbox_chan_ops mtk_vcp_mbox_chan_ops = { + .send_data = mtk_vcp_mbox_send_data, + .last_tx_done = mtk_vcp_mbox_last_tx_done, +}; + +static int mtk_vcp_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_vcp_mbox *priv; + struct mbox_controller *mbox; + int ret, irq; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->chans.con_priv = priv; + mbox = &priv->mbox; + mbox->dev = dev; + mbox->ops = &mtk_vcp_mbox_chan_ops; + mbox->txdone_irq = false; + mbox->txdone_poll = true; + mbox->of_xlate = mtk_vcp_mbox_xlate; + mbox->num_chans = 1; + mbox->chans = &priv->chans; + + priv->ipi_recv.msg = devm_kzalloc(dev, MTK_VCP_MBOX_SLOT_MAX_SIZE, + GFP_KERNEL); + if (!priv->ipi_recv.msg) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->cfg = of_device_get_match_data(dev); + if (!priv->cfg) + return -EINVAL; + + platform_set_drvdata(pdev, priv); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + mtk_vcp_mbox_irq_thread, IRQF_ONESHOT, + dev_name(dev), priv); + if (ret < 0) + return ret; + + return devm_mbox_controller_register(dev, &priv->mbox); +} + +static const struct mtk_vcp_mbox_cfg mt8196_cfg = { + .set_in = 0x100, + .clr_out = 0x10c, +}; + +static const struct of_device_id mtk_vcp_mbox_of_match[] = { + { .compatible = "mediatek,mt8196-vcp-mbox", .data = &mt8196_cfg }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_vcp_mbox_of_match); + +static struct platform_driver mtk_vcp_mbox_driver = { + .probe = mtk_vcp_mbox_probe, + .driver = { + .name = "mtk_vcp_mbox", + .of_match_table = mtk_vcp_mbox_of_match, + }, +}; +module_platform_driver(mtk_vcp_mbox_driver); + +MODULE_AUTHOR("Jjian Zhou <jjian.zhou@mediatek.com>"); +MODULE_DESCRIPTION("MTK VCP Mailbox Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/omap-mailbox.c b/drivers/mailbox/omap-mailbox.c new file mode 100644 index 000000000..535ca8020 --- /dev/null +++ b/drivers/mailbox/omap-mailbox.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OMAP mailbox driver + * + * Copyright (C) 2006-2009 Nokia Corporation. All rights reserved. + * Copyright (C) 2013-2021 Texas Instruments Incorporated - https://www.ti.com + * + * Contact: Hiroshi DOYU <Hiroshi.DOYU@nokia.com> + * Suman Anna <s-anna@ti.com> + */ + +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/kfifo.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/mailbox_controller.h> + +#define MAILBOX_REVISION 0x000 +#define MAILBOX_MESSAGE(m) (0x040 + 4 * (m)) +#define MAILBOX_FIFOSTATUS(m) (0x080 + 4 * (m)) +#define MAILBOX_MSGSTATUS(m) (0x0c0 + 4 * (m)) + +#define OMAP2_MAILBOX_IRQSTATUS(u) (0x100 + 8 * (u)) +#define OMAP2_MAILBOX_IRQENABLE(u) (0x104 + 8 * (u)) + +#define OMAP4_MAILBOX_IRQSTATUS(u) (0x104 + 0x10 * (u)) +#define OMAP4_MAILBOX_IRQENABLE(u) (0x108 + 0x10 * (u)) +#define OMAP4_MAILBOX_IRQENABLE_CLR(u) (0x10c + 0x10 * (u)) + +#define MAILBOX_IRQSTATUS(type, u) (type ? OMAP4_MAILBOX_IRQSTATUS(u) : \ + OMAP2_MAILBOX_IRQSTATUS(u)) +#define MAILBOX_IRQENABLE(type, u) (type ? OMAP4_MAILBOX_IRQENABLE(u) : \ + OMAP2_MAILBOX_IRQENABLE(u)) +#define MAILBOX_IRQDISABLE(type, u) (type ? OMAP4_MAILBOX_IRQENABLE_CLR(u) \ + : OMAP2_MAILBOX_IRQENABLE(u)) + +#define MAILBOX_IRQ_NEWMSG(m) (1 << (2 * (m))) +#define MAILBOX_IRQ_NOTFULL(m) (1 << (2 * (m) + 1)) + +/* Interrupt register configuration types */ +#define MBOX_INTR_CFG_TYPE1 0 +#define MBOX_INTR_CFG_TYPE2 1 + +typedef enum { + IRQ_TX = 1, + IRQ_RX = 2, +} omap_mbox_irq_t; + +struct omap_mbox_fifo { + unsigned long msg; + unsigned long fifo_stat; + unsigned long msg_stat; + unsigned long irqenable; + unsigned long irqstatus; + unsigned long irqdisable; + u32 intr_bit; +}; + +struct omap_mbox_match_data { + u32 intr_type; + bool is_exclusive; +}; + +struct omap_mbox_device { + struct device *dev; + struct mutex cfg_lock; + void __iomem *mbox_base; + u32 *irq_ctx; + u32 num_users; + u32 num_fifos; + u32 intr_type; + const struct omap_mbox_match_data *mbox_data; +}; + +struct omap_mbox { + const char *name; + int irq; + struct omap_mbox_device *parent; + struct omap_mbox_fifo tx_fifo; + struct omap_mbox_fifo rx_fifo; + u32 intr_type; + struct mbox_chan *chan; + bool send_no_irq; +}; + +static inline +unsigned int mbox_read_reg(struct omap_mbox_device *mdev, size_t ofs) +{ + return __raw_readl(mdev->mbox_base + ofs); +} + +static inline +void mbox_write_reg(struct omap_mbox_device *mdev, u32 val, size_t ofs) +{ + __raw_writel(val, mdev->mbox_base + ofs); +} + +/* Mailbox FIFO handle functions */ +static u32 mbox_fifo_read(struct omap_mbox *mbox) +{ + struct omap_mbox_fifo *fifo = &mbox->rx_fifo; + + return mbox_read_reg(mbox->parent, fifo->msg); +} + +static void mbox_fifo_write(struct omap_mbox *mbox, u32 msg) +{ + struct omap_mbox_fifo *fifo = &mbox->tx_fifo; + + mbox_write_reg(mbox->parent, msg, fifo->msg); +} + +static int mbox_fifo_empty(struct omap_mbox *mbox) +{ + struct omap_mbox_fifo *fifo = &mbox->rx_fifo; + + return (mbox_read_reg(mbox->parent, fifo->msg_stat) == 0); +} + +static int mbox_fifo_full(struct omap_mbox *mbox) +{ + struct omap_mbox_fifo *fifo = &mbox->tx_fifo; + + return mbox_read_reg(mbox->parent, fifo->fifo_stat); +} + +/* Mailbox IRQ handle functions */ +static void ack_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ + struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? + &mbox->tx_fifo : &mbox->rx_fifo; + u32 bit = fifo->intr_bit; + u32 irqstatus = fifo->irqstatus; + + mbox_write_reg(mbox->parent, bit, irqstatus); + + /* Flush posted write for irq status to avoid spurious interrupts */ + mbox_read_reg(mbox->parent, irqstatus); +} + +static int is_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ + struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? + &mbox->tx_fifo : &mbox->rx_fifo; + u32 bit = fifo->intr_bit; + u32 irqenable = fifo->irqenable; + u32 irqstatus = fifo->irqstatus; + + u32 enable = mbox_read_reg(mbox->parent, irqenable); + u32 status = mbox_read_reg(mbox->parent, irqstatus); + + return (int)(enable & status & bit); +} + +static void omap_mbox_enable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ + u32 l; + struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? + &mbox->tx_fifo : &mbox->rx_fifo; + u32 bit = fifo->intr_bit; + u32 irqenable = fifo->irqenable; + + l = mbox_read_reg(mbox->parent, irqenable); + l |= bit; + mbox_write_reg(mbox->parent, l, irqenable); +} + +static void omap_mbox_disable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ + struct omap_mbox_fifo *fifo = (irq == IRQ_TX) ? + &mbox->tx_fifo : &mbox->rx_fifo; + u32 bit = fifo->intr_bit; + u32 irqdisable = fifo->irqdisable; + + /* + * Read and update the interrupt configuration register for pre-OMAP4. + * OMAP4 and later SoCs have a dedicated interrupt disabling register. + */ + if (!mbox->intr_type) + bit = mbox_read_reg(mbox->parent, irqdisable) & ~bit; + + mbox_write_reg(mbox->parent, bit, irqdisable); +} + +/* + * Mailbox interrupt handler + */ +static void __mbox_tx_interrupt(struct omap_mbox *mbox) +{ + omap_mbox_disable_irq(mbox, IRQ_TX); + ack_mbox_irq(mbox, IRQ_TX); + mbox_chan_txdone(mbox->chan, 0); +} + +static void __mbox_rx_interrupt(struct omap_mbox *mbox) +{ + u32 msg; + + while (!mbox_fifo_empty(mbox)) { + msg = mbox_fifo_read(mbox); + mbox_chan_received_data(mbox->chan, (void *)(uintptr_t)msg); + } + + /* clear IRQ source. */ + ack_mbox_irq(mbox, IRQ_RX); +} + +static irqreturn_t mbox_interrupt(int irq, void *p) +{ + struct omap_mbox *mbox = p; + + if (is_mbox_irq(mbox, IRQ_TX)) + __mbox_tx_interrupt(mbox); + + if (is_mbox_irq(mbox, IRQ_RX)) + __mbox_rx_interrupt(mbox); + + return IRQ_HANDLED; +} + +static int omap_mbox_startup(struct omap_mbox *mbox) +{ + int ret = 0; + + ret = request_threaded_irq(mbox->irq, NULL, mbox_interrupt, + IRQF_SHARED | IRQF_ONESHOT, mbox->name, + mbox); + if (unlikely(ret)) { + pr_err("failed to register mailbox interrupt:%d\n", ret); + return ret; + } + + if (mbox->send_no_irq) + mbox->chan->txdone_method = MBOX_TXDONE_BY_ACK; + + omap_mbox_enable_irq(mbox, IRQ_RX); + + return 0; +} + +static void omap_mbox_fini(struct omap_mbox *mbox) +{ + omap_mbox_disable_irq(mbox, IRQ_RX); + free_irq(mbox->irq, mbox); +} + +static int omap_mbox_chan_startup(struct mbox_chan *chan) +{ + struct omap_mbox *mbox = chan->con_priv; + struct omap_mbox_device *mdev = mbox->parent; + int ret = 0; + + mutex_lock(&mdev->cfg_lock); + pm_runtime_get_sync(mdev->dev); + ret = omap_mbox_startup(mbox); + if (ret) + pm_runtime_put_sync(mdev->dev); + mutex_unlock(&mdev->cfg_lock); + return ret; +} + +static void omap_mbox_chan_shutdown(struct mbox_chan *chan) +{ + struct omap_mbox *mbox = chan->con_priv; + struct omap_mbox_device *mdev = mbox->parent; + + mutex_lock(&mdev->cfg_lock); + omap_mbox_fini(mbox); + pm_runtime_put_sync(mdev->dev); + mutex_unlock(&mdev->cfg_lock); +} + +static int omap_mbox_chan_send_noirq(struct omap_mbox *mbox, u32 msg) +{ + if (mbox_fifo_full(mbox)) + return -EBUSY; + + omap_mbox_enable_irq(mbox, IRQ_RX); + mbox_fifo_write(mbox, msg); + omap_mbox_disable_irq(mbox, IRQ_RX); + + /* we must read and ack the interrupt directly from here */ + mbox_fifo_read(mbox); + ack_mbox_irq(mbox, IRQ_RX); + + return 0; +} + +static int omap_mbox_chan_send(struct omap_mbox *mbox, u32 msg) +{ + if (mbox_fifo_full(mbox)) { + /* always enable the interrupt */ + omap_mbox_enable_irq(mbox, IRQ_TX); + return -EBUSY; + } + + mbox_fifo_write(mbox, msg); + + /* always enable the interrupt */ + omap_mbox_enable_irq(mbox, IRQ_TX); + return 0; +} + +static int omap_mbox_chan_send_data(struct mbox_chan *chan, void *data) +{ + struct omap_mbox *mbox = chan->con_priv; + int ret; + u32 msg = (u32)(uintptr_t)(data); + + if (!mbox) + return -EINVAL; + + if (mbox->send_no_irq) + ret = omap_mbox_chan_send_noirq(mbox, msg); + else + ret = omap_mbox_chan_send(mbox, msg); + + return ret; +} + +static const struct mbox_chan_ops omap_mbox_chan_ops = { + .startup = omap_mbox_chan_startup, + .send_data = omap_mbox_chan_send_data, + .shutdown = omap_mbox_chan_shutdown, +}; + +#ifdef CONFIG_PM_SLEEP +static int omap_mbox_suspend(struct device *dev) +{ + struct omap_mbox_device *mdev = dev_get_drvdata(dev); + u32 usr, fifo, reg; + + if (pm_runtime_status_suspended(dev)) + return 0; + + if (mdev->mbox_data->is_exclusive) { + for (fifo = 0; fifo < mdev->num_fifos; fifo++) { + if (mbox_read_reg(mdev, MAILBOX_MSGSTATUS(fifo))) { + dev_err(mdev->dev, "fifo %d has unexpected unread messages\n", + fifo); + return -EBUSY; + } + } + } + + for (usr = 0; usr < mdev->num_users; usr++) { + reg = MAILBOX_IRQENABLE(mdev->intr_type, usr); + mdev->irq_ctx[usr] = mbox_read_reg(mdev, reg); + } + + return 0; +} + +static int omap_mbox_resume(struct device *dev) +{ + struct omap_mbox_device *mdev = dev_get_drvdata(dev); + u32 usr, reg; + + if (pm_runtime_status_suspended(dev)) + return 0; + + for (usr = 0; usr < mdev->num_users; usr++) { + reg = MAILBOX_IRQENABLE(mdev->intr_type, usr); + mbox_write_reg(mdev, mdev->irq_ctx[usr], reg); + } + + return 0; +} +#endif + +static const struct dev_pm_ops omap_mbox_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(omap_mbox_suspend, omap_mbox_resume) +}; + +static const struct omap_mbox_match_data omap2_data = { MBOX_INTR_CFG_TYPE1, true }; +static const struct omap_mbox_match_data omap4_data = { MBOX_INTR_CFG_TYPE2, true }; +static const struct omap_mbox_match_data am654_data = { MBOX_INTR_CFG_TYPE2, false }; + +static const struct of_device_id omap_mailbox_of_match[] = { + { + .compatible = "ti,omap2-mailbox", + .data = &omap2_data, + }, + { + .compatible = "ti,omap3-mailbox", + .data = &omap2_data, + }, + { + .compatible = "ti,omap4-mailbox", + .data = &omap4_data, + }, + { + .compatible = "ti,am654-mailbox", + .data = &am654_data, + }, + { + .compatible = "ti,am64-mailbox", + .data = &am654_data, + }, + { + /* end */ + }, +}; +MODULE_DEVICE_TABLE(of, omap_mailbox_of_match); + +static struct mbox_chan *omap_mbox_of_xlate(struct mbox_controller *controller, + const struct of_phandle_args *sp) +{ + phandle phandle = sp->args[0]; + struct device_node *node; + struct omap_mbox_device *mdev; + struct omap_mbox *mbox; + int i; + + mdev = dev_get_drvdata(controller->dev); + if (WARN_ON(!mdev)) + return ERR_PTR(-EINVAL); + + node = of_find_node_by_phandle(phandle); + if (!node) { + pr_err("%s: could not find node phandle 0x%x\n", + __func__, phandle); + return ERR_PTR(-ENODEV); + } + + for (i = 0; i < controller->num_chans; i++) { + mbox = controller->chans[i].con_priv; + if (!strcmp(mbox->name, node->name)) { + of_node_put(node); + return &controller->chans[i]; + } + } + + of_node_put(node); + return ERR_PTR(-ENOENT); +} + +static int omap_mbox_probe(struct platform_device *pdev) +{ + int ret; + struct mbox_chan *chnls; + struct omap_mbox *mbox; + struct omap_mbox_device *mdev; + struct omap_mbox_fifo *fifo; + struct device_node *node = pdev->dev.of_node; + struct device_node *child; + struct mbox_controller *controller; + u32 intr_type, info_count; + u32 num_users, num_fifos; + u32 tmp[3]; + u32 l; + int i; + + if (!node) { + pr_err("%s: only DT-based devices are supported\n", __func__); + return -ENODEV; + } + + if (of_property_read_u32(node, "ti,mbox-num-users", &num_users)) + return -ENODEV; + + if (of_property_read_u32(node, "ti,mbox-num-fifos", &num_fifos)) + return -ENODEV; + + info_count = of_get_available_child_count(node); + if (!info_count) { + dev_err(&pdev->dev, "no available mbox devices found\n"); + return -ENODEV; + } + + mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + mdev->mbox_data = device_get_match_data(&pdev->dev); + if (!mdev->mbox_data) + return -ENODEV; + + intr_type = mdev->mbox_data->intr_type; + + mdev->mbox_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mdev->mbox_base)) + return PTR_ERR(mdev->mbox_base); + + mdev->irq_ctx = devm_kcalloc(&pdev->dev, num_users, sizeof(u32), + GFP_KERNEL); + if (!mdev->irq_ctx) + return -ENOMEM; + + chnls = devm_kcalloc(&pdev->dev, info_count + 1, sizeof(*chnls), + GFP_KERNEL); + if (!chnls) + return -ENOMEM; + + child = NULL; + for (i = 0; i < info_count; i++) { + int tx_id, tx_irq, tx_usr; + int rx_id, rx_usr; + + mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + child = of_get_next_available_child(node, child); + ret = of_property_read_u32_array(child, "ti,mbox-tx", tmp, + ARRAY_SIZE(tmp)); + if (ret) + return ret; + tx_id = tmp[0]; + tx_irq = tmp[1]; + tx_usr = tmp[2]; + + ret = of_property_read_u32_array(child, "ti,mbox-rx", tmp, + ARRAY_SIZE(tmp)); + if (ret) + return ret; + rx_id = tmp[0]; + /* rx_irq = tmp[1]; */ + rx_usr = tmp[2]; + + if (tx_id >= num_fifos || rx_id >= num_fifos || + tx_usr >= num_users || rx_usr >= num_users) + return -EINVAL; + + fifo = &mbox->tx_fifo; + fifo->msg = MAILBOX_MESSAGE(tx_id); + fifo->fifo_stat = MAILBOX_FIFOSTATUS(tx_id); + fifo->intr_bit = MAILBOX_IRQ_NOTFULL(tx_id); + fifo->irqenable = MAILBOX_IRQENABLE(intr_type, tx_usr); + fifo->irqstatus = MAILBOX_IRQSTATUS(intr_type, tx_usr); + fifo->irqdisable = MAILBOX_IRQDISABLE(intr_type, tx_usr); + + fifo = &mbox->rx_fifo; + fifo->msg = MAILBOX_MESSAGE(rx_id); + fifo->msg_stat = MAILBOX_MSGSTATUS(rx_id); + fifo->intr_bit = MAILBOX_IRQ_NEWMSG(rx_id); + fifo->irqenable = MAILBOX_IRQENABLE(intr_type, rx_usr); + fifo->irqstatus = MAILBOX_IRQSTATUS(intr_type, rx_usr); + fifo->irqdisable = MAILBOX_IRQDISABLE(intr_type, rx_usr); + + mbox->send_no_irq = of_property_read_bool(child, "ti,mbox-send-noirq"); + mbox->intr_type = intr_type; + + mbox->parent = mdev; + mbox->name = child->name; + mbox->irq = platform_get_irq(pdev, tx_irq); + if (mbox->irq < 0) + return mbox->irq; + mbox->chan = &chnls[i]; + chnls[i].con_priv = mbox; + } + + mutex_init(&mdev->cfg_lock); + mdev->dev = &pdev->dev; + mdev->num_users = num_users; + mdev->num_fifos = num_fifos; + mdev->intr_type = intr_type; + + controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + /* + * OMAP/K3 Mailbox IP does not have a Tx-Done IRQ, but rather a Tx-Ready + * IRQ and is needed to run the Tx state machine + */ + controller->txdone_irq = true; + controller->dev = mdev->dev; + controller->ops = &omap_mbox_chan_ops; + controller->chans = chnls; + controller->num_chans = info_count; + controller->of_xlate = omap_mbox_of_xlate; + ret = devm_mbox_controller_register(mdev->dev, controller); + if (ret) + return ret; + + platform_set_drvdata(pdev, mdev); + devm_pm_runtime_enable(mdev->dev); + + ret = pm_runtime_resume_and_get(mdev->dev); + if (ret < 0) + return ret; + + /* + * just print the raw revision register, the format is not + * uniform across all SoCs + */ + l = mbox_read_reg(mdev, MAILBOX_REVISION); + dev_info(mdev->dev, "omap mailbox rev 0x%x\n", l); + + ret = pm_runtime_put_sync(mdev->dev); + if (ret < 0 && ret != -ENOSYS) + return ret; + + return 0; +} + +static struct platform_driver omap_mbox_driver = { + .probe = omap_mbox_probe, + .driver = { + .name = "omap-mailbox", + .pm = &omap_mbox_pm_ops, + .of_match_table = omap_mailbox_of_match, + }, +}; +module_platform_driver(omap_mbox_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("omap mailbox: interrupt driven messaging"); +MODULE_AUTHOR("Toshihiro Kobayashi"); +MODULE_AUTHOR("Hiroshi DOYU"); diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c new file mode 100644 index 000000000..9888dab64 --- /dev/null +++ b/drivers/mailbox/pcc.c @@ -0,0 +1,944 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014 Linaro Ltd. + * Author: Ashwin Chaugule <ashwin.chaugule@linaro.org> + * + * PCC (Platform Communication Channel) is defined in the ACPI 5.0+ + * specification. It is a mailbox like mechanism to allow clients + * such as CPPC (Collaborative Processor Performance Control), RAS + * (Reliability, Availability and Serviceability) and MPST (Memory + * Node Power State Table) to talk to the platform (e.g. BMC) through + * shared memory regions as defined in the PCC table entries. The PCC + * specification supports a Doorbell mechanism for the PCC clients + * to notify the platform about new data. This Doorbell information + * is also specified in each PCC table entry. + * + * Typical high level flow of operation is: + * + * PCC Reads: + * * Client tries to acquire a channel lock. + * * After it is acquired it writes READ cmd in communication region cmd + * address. + * * Client issues mbox_send_message() which rings the PCC doorbell + * for its PCC channel. + * * If command completes, then client has control over channel and + * it can proceed with its reads. + * * Client releases lock. + * + * PCC Writes: + * * Client tries to acquire channel lock. + * * Client writes to its communication region after it acquires a + * channel lock. + * * Client writes WRITE cmd in communication region cmd address. + * * Client issues mbox_send_message() which rings the PCC doorbell + * for its PCC channel. + * * If command completes, then writes have succeeded and it can release + * the channel lock. + * + * There is a Nominal latency defined for each channel which indicates + * how long to wait until a command completes. If command is not complete + * the client needs to retry or assume failure. + * + * For more details about PCC, please see the ACPI specification from + * http://www.uefi.org/ACPIv5.1 Section 14. + * + * This file implements PCC as a Mailbox controller and allows for PCC + * clients to be implemented as its Mailbox Client Channels. + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/log2.h> +#include <linux/platform_device.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox_client.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <acpi/pcc.h> + +#define MBOX_IRQ_NAME "pcc-mbox" + +/** + * struct pcc_chan_reg - PCC register bundle + * + * @vaddr: cached virtual address for this register + * @gas: pointer to the generic address structure for this register + * @preserve_mask: bitmask to preserve when writing to this register + * @set_mask: bitmask to set when writing to this register + * @status_mask: bitmask to determine and/or update the status for this register + */ +struct pcc_chan_reg { + void __iomem *vaddr; + struct acpi_generic_address *gas; + u64 preserve_mask; + u64 set_mask; + u64 status_mask; +}; + +/** + * struct pcc_chan_info - PCC channel specific information + * + * @chan: PCC channel information with Shared Memory Region info + * @db: PCC register bundle for the doorbell register + * @plat_irq_ack: PCC register bundle for the platform interrupt acknowledge + * register + * @cmd_complete: PCC register bundle for the command complete check register + * @cmd_update: PCC register bundle for the command complete update register + * @error: PCC register bundle for the error status register + * @plat_irq: platform interrupt + * @type: PCC subspace type + * @plat_irq_flags: platform interrupt flags + * @chan_in_use: lockless flag used by type 3 initiator subspaces to filter + * platform interrupts. Only one transfer can occur at a time, but + * the interrupt handler may sample the flag on another CPU, so all + * accesses must use READ_ONCE() or WRITE_ONCE(). Other subspace + * types do not test it. + */ +struct pcc_chan_info { + struct pcc_mbox_chan chan; + struct pcc_chan_reg db; + struct pcc_chan_reg plat_irq_ack; + struct pcc_chan_reg cmd_complete; + struct pcc_chan_reg cmd_update; + struct pcc_chan_reg error; + int plat_irq; + u8 type; + unsigned int plat_irq_flags; + bool chan_in_use; +}; + +#define to_pcc_chan_info(c) container_of(c, struct pcc_chan_info, chan) +static struct pcc_chan_info *chan_info; +static int pcc_chan_count; + +/* + * PCC can be used with perf critical drivers such as CPPC + * So it makes sense to locally cache the virtual address and + * use it to read/write to PCC registers such as doorbell register + * + * The below read_register and write_registers are used to read and + * write from perf critical registers such as PCC doorbell register + */ +static void read_register(void __iomem *vaddr, u64 *val, unsigned int bit_width) +{ + switch (bit_width) { + case 8: + *val = readb(vaddr); + break; + case 16: + *val = readw(vaddr); + break; + case 32: + *val = readl(vaddr); + break; + case 64: + *val = readq(vaddr); + break; + } +} + +static void write_register(void __iomem *vaddr, u64 val, unsigned int bit_width) +{ + switch (bit_width) { + case 8: + writeb(val, vaddr); + break; + case 16: + writew(val, vaddr); + break; + case 32: + writel(val, vaddr); + break; + case 64: + writeq(val, vaddr); + break; + } +} + +static int pcc_chan_reg_read(struct pcc_chan_reg *reg, u64 *val) +{ + int ret = 0; + + if (!reg->gas) { + *val = 0; + return 0; + } + + if (reg->vaddr) + read_register(reg->vaddr, val, reg->gas->bit_width); + else + ret = acpi_read(val, reg->gas); + + return ret; +} + +static int pcc_chan_reg_write(struct pcc_chan_reg *reg, u64 val) +{ + int ret = 0; + + if (!reg->gas) + return 0; + + if (reg->vaddr) + write_register(reg->vaddr, val, reg->gas->bit_width); + else + ret = acpi_write(val, reg->gas); + + return ret; +} + +static int pcc_chan_reg_read_modify_write(struct pcc_chan_reg *reg) +{ + int ret = 0; + u64 val; + + ret = pcc_chan_reg_read(reg, &val); + if (ret) + return ret; + + val &= reg->preserve_mask; + val |= reg->set_mask; + + return pcc_chan_reg_write(reg, val); +} + +/** + * pcc_map_interrupt - Map a PCC subspace GSI to a linux IRQ number + * @interrupt: GSI number. + * @flags: interrupt flags + * + * Returns: a valid linux IRQ number on success + * 0 or -EINVAL on failure + */ +static int pcc_map_interrupt(u32 interrupt, u32 flags) +{ + int trigger, polarity; + + if (!interrupt) + return 0; + + trigger = (flags & ACPI_PCCT_INTERRUPT_MODE) ? ACPI_EDGE_SENSITIVE + : ACPI_LEVEL_SENSITIVE; + + polarity = (flags & ACPI_PCCT_INTERRUPT_POLARITY) ? ACPI_ACTIVE_LOW + : ACPI_ACTIVE_HIGH; + + return acpi_register_gsi(NULL, interrupt, trigger, polarity); +} + +static bool pcc_chan_plat_irq_can_be_shared(struct pcc_chan_info *pchan) +{ + return (pchan->plat_irq_flags & ACPI_PCCT_INTERRUPT_MODE) == + ACPI_LEVEL_SENSITIVE; +} + +static bool pcc_mbox_cmd_complete_check(struct pcc_chan_info *pchan) +{ + u64 val; + int ret; + + if (!pchan->cmd_complete.gas) + return true; + + ret = pcc_chan_reg_read(&pchan->cmd_complete, &val); + if (ret) + return false; + + /* + * Judge if the channel respond the interrupt based on the value of + * command complete. + */ + val &= pchan->cmd_complete.status_mask; + + /* + * If this is PCC slave subspace channel, and the command complete + * bit 0 indicates that Platform is sending a notification and OSPM + * needs to respond this interrupt to process this command. + */ + if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + return !val; + + return !!val; +} + +static int pcc_mbox_error_check_and_clear(struct pcc_chan_info *pchan) +{ + u64 val; + int ret; + + ret = pcc_chan_reg_read(&pchan->error, &val); + if (ret) + return ret; + + if (val & pchan->error.status_mask) { + val &= pchan->error.preserve_mask; + pcc_chan_reg_write(&pchan->error, val); + return -EIO; + } + + return 0; +} + +static void pcc_chan_acknowledge(struct pcc_chan_info *pchan) +{ + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_hdr; + + if (pchan->type != ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + return; + + pcc_chan_reg_read_modify_write(&pchan->cmd_update); + + pcc_hdr = pchan->chan.shmem; + + /* + * The PCC slave subspace channel needs to set the command + * complete bit after processing message. If the PCC_ACK_FLAG + * is set, it should also ring the doorbell. + */ + if (ioread32(&pcc_hdr->flags) & PCC_CMD_COMPLETION_NOTIFY) + pcc_chan_reg_read_modify_write(&pchan->db); +} + +/** + * pcc_mbox_irq - PCC mailbox interrupt handler + * @irq: interrupt number + * @p: data/cookie passed from the caller to identify the channel + * + * Returns: IRQ_HANDLED if interrupt is handled or IRQ_NONE if not + */ +static irqreturn_t pcc_mbox_irq(int irq, void *p) +{ + struct pcc_chan_info *pchan; + struct mbox_chan *chan = p; + + pchan = chan->con_priv; + + if (pcc_chan_reg_read_modify_write(&pchan->plat_irq_ack)) + return IRQ_NONE; + + /* + * Initiator subspaces use this flag to filter shared interrupts. Use + * READ_ONCE() to sample the lockless flag written by pcc_send_data() + * on another CPU. + */ + if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE && + !READ_ONCE(pchan->chan_in_use)) + return IRQ_NONE; + + if (!pcc_mbox_cmd_complete_check(pchan)) + return IRQ_NONE; + + if (pcc_mbox_error_check_and_clear(pchan)) + return IRQ_NONE; + + /* + * Clear this flag after updating the interrupt ack register and before + * notifying the client and mailbox core. mbox_chan_txdone() may submit + * the next queued transfer and set the flag again. Use WRITE_ONCE() for + * the lockless update observed by the send and interrupt paths. + */ + WRITE_ONCE(pchan->chan_in_use, false); + mbox_chan_received_data(chan, NULL); + mbox_chan_txdone(chan, 0); + + pcc_chan_acknowledge(pchan); + + return IRQ_HANDLED; +} + +static int pcc_mbox_validate_signature(struct pcc_mbox_chan *pcc_mchan, + int subspace_id) +{ + u32 expected_signature = PCC_SIGNATURE | subspace_id; + u32 signature; + + if (pcc_mchan->shmem_size < sizeof(signature)) { + pr_err("PCC subspace %d shared memory is too small\n", + subspace_id); + return -EINVAL; + } + + signature = ioread32(pcc_mchan->shmem); + if (signature != expected_signature) + pr_warn("PCC subspace %d invalid signature %#x expected %#x\n", + subspace_id, signature, expected_signature); + + return 0; +} + +/** + * pcc_mbox_request_channel - PCC clients call this function to + * request a pointer to their PCC subspace, from which they + * can get the details of communicating with the remote. + * @cl: Pointer to Mailbox client, so we know where to bind the + * Channel. + * @subspace_id: The PCC Subspace index as parsed in the PCC client + * ACPI package. This is used to lookup the array of PCC + * subspaces as parsed by the PCC Mailbox controller. + * + * Return: Pointer to the PCC Mailbox Channel if successful or ERR_PTR. + */ +struct pcc_mbox_chan * +pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) +{ + struct pcc_mbox_chan *pcc_mchan; + struct pcc_chan_info *pchan; + struct mbox_chan *chan; + int rc; + + if (subspace_id < 0 || subspace_id >= pcc_chan_count) + return ERR_PTR(-ENOENT); + + pchan = chan_info + subspace_id; + chan = pchan->chan.mchan; + if (IS_ERR(chan) || chan->cl) { + pr_err("Channel not found for idx: %d\n", subspace_id); + return ERR_PTR(-EBUSY); + } + + pcc_mchan = &pchan->chan; + pcc_mchan->shmem = acpi_os_ioremap(pcc_mchan->shmem_base_addr, + pcc_mchan->shmem_size); + if (!pcc_mchan->shmem) + return ERR_PTR(-ENXIO); + + rc = pcc_mbox_validate_signature(pcc_mchan, subspace_id); + if (rc) + goto err_unmap_shmem; + + rc = mbox_bind_client(chan, cl); + if (rc) + goto err_unmap_shmem; + + return pcc_mchan; + +err_unmap_shmem: + iounmap(pcc_mchan->shmem); + pcc_mchan->shmem = NULL; + return ERR_PTR(rc); +} +EXPORT_SYMBOL_GPL(pcc_mbox_request_channel); + +/** + * pcc_mbox_free_channel - Clients call this to free their Channel. + * + * @pchan: Pointer to the PCC mailbox channel as returned by + * pcc_mbox_request_channel() + */ +void pcc_mbox_free_channel(struct pcc_mbox_chan *pchan) +{ + struct mbox_chan *chan = pchan->mchan; + struct pcc_chan_info *pchan_info; + struct pcc_mbox_chan *pcc_mbox_chan; + + if (!chan || !chan->cl) + return; + pchan_info = chan->con_priv; + pcc_mbox_chan = &pchan_info->chan; + if (pcc_mbox_chan->shmem) { + iounmap(pcc_mbox_chan->shmem); + pcc_mbox_chan->shmem = NULL; + } + + mbox_free_channel(chan); +} +EXPORT_SYMBOL_GPL(pcc_mbox_free_channel); + +/** + * pcc_send_data - Called from Mailbox Controller code. 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 + * OS has control over it. See above for flow of operations. + * @chan: Pointer to Mailbox channel over which to send data. + * @data: Client specific data written over channel. Used here + * only for debug after PCC transaction completes. + * + * Return: Err if something failed else 0 for success. + */ +static int pcc_send_data(struct mbox_chan *chan, void *data) +{ + int ret; + struct pcc_chan_info *pchan = chan->con_priv; + + ret = pcc_chan_reg_read_modify_write(&pchan->cmd_update); + if (ret) + return ret; + + /* + * Set chan_in_use before ringing the doorbell so a fast completion + * interrupt is not mistaken for a shared interrupt from another + * subspace. Use WRITE_ONCE() for the lockless flag update. The + * ordered I/O accessor used to ring the doorbell orders this store + * before the platform is notified. + */ + if (pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, true); + ret = pcc_chan_reg_read_modify_write(&pchan->db); + if (ret && pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, false); + + return ret; +} + +static bool pcc_last_tx_done(struct mbox_chan *chan) +{ + struct pcc_chan_info *pchan = chan->con_priv; + + if (!(chan->txdone_method & MBOX_TXDONE_BY_POLL)) + return false; + + if (!pcc_mbox_cmd_complete_check(pchan)) + return false; + + mbox_chan_received_data(chan, NULL); + + return true; +} + +/** + * pcc_startup - Called from Mailbox Controller code. Used here + * to request the interrupt. + * @chan: Pointer to Mailbox channel to startup. + * + * Return: Err if something failed else 0 for success. + */ +static int pcc_startup(struct mbox_chan *chan) +{ + struct pcc_chan_info *pchan = chan->con_priv; + unsigned long irqflags; + int rc; + + /* + * Clear and acknowledge any pending interrupts on responder channel + * before enabling the interrupt + */ + pcc_chan_acknowledge(pchan); + + if (pchan->plat_irq > 0) { + irqflags = pcc_chan_plat_irq_can_be_shared(pchan) ? + IRQF_SHARED : 0; + rc = devm_request_irq(chan->mbox->dev, pchan->plat_irq, pcc_mbox_irq, + irqflags, MBOX_IRQ_NAME, chan); + if (unlikely(rc)) { + dev_err(chan->mbox->dev, "failed to register PCC interrupt %d\n", + pchan->plat_irq); + return rc; + } + } + + return 0; +} + +/** + * pcc_shutdown - Called from Mailbox Controller code. Used here + * to free the interrupt. + * @chan: Pointer to Mailbox channel to shutdown. + */ +static void pcc_shutdown(struct mbox_chan *chan) +{ + struct pcc_chan_info *pchan = chan->con_priv; + + if (pchan->plat_irq > 0) + devm_free_irq(chan->mbox->dev, pchan->plat_irq, chan); +} + +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, +}; + +/** + * parse_pcc_subspace - Count PCC subspaces defined + * @header: Pointer to the ACPI subtable header under the PCCT. + * @end: End of subtable entry. + * + * Return: If we find a PCC subspace entry of a valid type, return 0. + * Otherwise, return -EINVAL. + * + * This gets called for each entry in the PCC table. + */ +static int parse_pcc_subspace(union acpi_subtable_headers *header, + const unsigned long end) +{ + struct acpi_pcct_subspace *ss = (struct acpi_pcct_subspace *) header; + + if (ss->header.type < ACPI_PCCT_TYPE_RESERVED) + return 0; + + return -EINVAL; +} + +static int +pcc_chan_reg_init(struct pcc_chan_reg *reg, struct acpi_generic_address *gas, + u64 preserve_mask, u64 set_mask, u64 status_mask, char *name) +{ + if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { + if (!(gas->bit_width >= 8 && gas->bit_width <= 64 && + is_power_of_2(gas->bit_width))) { + pr_err("Error: Cannot access register of %u bit width", + gas->bit_width); + return -EFAULT; + } + + reg->vaddr = acpi_os_ioremap(gas->address, gas->bit_width / 8); + if (!reg->vaddr) { + pr_err("Failed to ioremap PCC %s register\n", name); + return -ENOMEM; + } + } + reg->gas = gas; + reg->preserve_mask = preserve_mask; + reg->set_mask = set_mask; + reg->status_mask = status_mask; + return 0; +} + +/** + * pcc_parse_subspace_irq - Parse the PCC IRQ and PCC ACK register + * + * @pchan: Pointer to the PCC channel info structure. + * @pcct_entry: Pointer to the ACPI subtable header. + * + * Return: 0 for Success, else errno. + * + * There should be one entry per PCC channel. This gets called for each + * entry in the PCC table. This uses PCCY Type1 structure for all applicable + * types(Type 1-4) to fetch irq + */ +static int pcc_parse_subspace_irq(struct pcc_chan_info *pchan, + struct acpi_subtable_header *pcct_entry) +{ + int ret = 0; + struct acpi_pcct_hw_reduced *pcct_ss; + + if (pcct_entry->type < ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE || + pcct_entry->type > ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + return 0; + + pcct_ss = (struct acpi_pcct_hw_reduced *)pcct_entry; + pchan->plat_irq = pcc_map_interrupt(pcct_ss->platform_interrupt, + (u32)pcct_ss->flags); + if (pchan->plat_irq <= 0) { + pr_err("PCC GSI %d not registered\n", + pcct_ss->platform_interrupt); + return -EINVAL; + } + pchan->plat_irq_flags = pcct_ss->flags; + + if (pcct_ss->header.type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) { + struct acpi_pcct_hw_reduced_type2 *pcct2_ss = (void *)pcct_ss; + + ret = pcc_chan_reg_init(&pchan->plat_irq_ack, + &pcct2_ss->platform_ack_register, + pcct2_ss->ack_preserve_mask, + pcct2_ss->ack_write_mask, 0, + "PLAT IRQ ACK"); + + } else if (pcct_ss->header.type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE || + pcct_ss->header.type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) { + struct acpi_pcct_ext_pcc_master *pcct_ext = (void *)pcct_ss; + + ret = pcc_chan_reg_init(&pchan->plat_irq_ack, + &pcct_ext->platform_ack_register, + pcct_ext->ack_preserve_mask, + pcct_ext->ack_set_mask, 0, + "PLAT IRQ ACK"); + } + + if (pcc_chan_plat_irq_can_be_shared(pchan) && + !pchan->plat_irq_ack.gas) { + pr_err("PCC subspace has level IRQ with no ACK register\n"); + return -EINVAL; + } + + return ret; +} + +/** + * pcc_parse_subspace_db_reg - Parse the PCC doorbell register + * + * @pchan: Pointer to the PCC channel info structure. + * @pcct_entry: Pointer to the ACPI subtable header. + * + * Return: 0 for Success, else errno. + */ +static int pcc_parse_subspace_db_reg(struct pcc_chan_info *pchan, + struct acpi_subtable_header *pcct_entry) +{ + int ret = 0; + + if (pcct_entry->type <= ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) { + struct acpi_pcct_subspace *pcct_ss; + + pcct_ss = (struct acpi_pcct_subspace *)pcct_entry; + + ret = pcc_chan_reg_init(&pchan->db, + &pcct_ss->doorbell_register, + pcct_ss->preserve_mask, + pcct_ss->write_mask, 0, "Doorbell"); + + } else { + struct acpi_pcct_ext_pcc_master *pcct_ext; + + pcct_ext = (struct acpi_pcct_ext_pcc_master *)pcct_entry; + + ret = pcc_chan_reg_init(&pchan->db, + &pcct_ext->doorbell_register, + pcct_ext->preserve_mask, + pcct_ext->write_mask, 0, "Doorbell"); + if (ret) + return ret; + + ret = pcc_chan_reg_init(&pchan->cmd_complete, + &pcct_ext->cmd_complete_register, + 0, 0, pcct_ext->cmd_complete_mask, + "Command Complete Check"); + if (ret) + return ret; + + ret = pcc_chan_reg_init(&pchan->cmd_update, + &pcct_ext->cmd_update_register, + pcct_ext->cmd_update_preserve_mask, + pcct_ext->cmd_update_set_mask, 0, + "Command Complete Update"); + if (ret) + return ret; + + ret = pcc_chan_reg_init(&pchan->error, + &pcct_ext->error_status_register, + ~pcct_ext->error_status_mask, 0, + pcct_ext->error_status_mask, + "Error Status"); + } + return ret; +} + +/** + * pcc_parse_subspace_shmem - Parse the PCC Shared Memory Region information + * + * @pchan: Pointer to the PCC channel info structure. + * @pcct_entry: Pointer to the ACPI subtable header. + * + */ +static void pcc_parse_subspace_shmem(struct pcc_chan_info *pchan, + struct acpi_subtable_header *pcct_entry) +{ + if (pcct_entry->type <= ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) { + struct acpi_pcct_subspace *pcct_ss = + (struct acpi_pcct_subspace *)pcct_entry; + + pchan->chan.shmem_base_addr = pcct_ss->base_address; + pchan->chan.shmem_size = pcct_ss->length; + pchan->chan.latency = pcct_ss->latency; + pchan->chan.max_access_rate = pcct_ss->max_access_rate; + pchan->chan.min_turnaround_time = pcct_ss->min_turnaround_time; + } else { + struct acpi_pcct_ext_pcc_master *pcct_ext = + (struct acpi_pcct_ext_pcc_master *)pcct_entry; + + pchan->chan.shmem_base_addr = pcct_ext->base_address; + pchan->chan.shmem_size = pcct_ext->length; + pchan->chan.latency = pcct_ext->latency; + pchan->chan.max_access_rate = pcct_ext->max_access_rate; + pchan->chan.min_turnaround_time = pcct_ext->min_turnaround_time; + } +} + +/** + * acpi_pcc_probe - Parse the ACPI tree for the PCCT. + * + * Return: 0 for Success, else errno. + */ +static int __init acpi_pcc_probe(void) +{ + int count, i, rc = 0; + acpi_status status; + struct acpi_table_header *pcct_tbl; + struct acpi_subtable_proc proc[ACPI_PCCT_TYPE_RESERVED]; + + status = acpi_get_table(ACPI_SIG_PCCT, 0, &pcct_tbl); + if (ACPI_FAILURE(status) || !pcct_tbl) + return -ENODEV; + + /* Set up the subtable handlers */ + for (i = ACPI_PCCT_TYPE_GENERIC_SUBSPACE; + i < ACPI_PCCT_TYPE_RESERVED; i++) { + proc[i].id = i; + proc[i].count = 0; + proc[i].handler = parse_pcc_subspace; + } + + count = acpi_table_parse_entries_array(ACPI_SIG_PCCT, + sizeof(struct acpi_table_pcct), proc, + ACPI_PCCT_TYPE_RESERVED, MAX_PCC_SUBSPACES); + if (count <= 0 || count > MAX_PCC_SUBSPACES) { + if (count < 0) + pr_warn("Error parsing PCC subspaces from PCCT\n"); + else + pr_warn("Invalid PCCT: %d PCC subspaces\n", count); + + rc = -EINVAL; + } else { + pcc_chan_count = count; + } + + acpi_put_table(pcct_tbl); + + return rc; +} + +/** + * pcc_mbox_probe - Called when we find a match for the + * PCCT platform device. This is purely used to represent + * the PCCT as a virtual device for registering with the + * generic Mailbox framework. + * + * @pdev: Pointer to platform device returned when a match + * is found. + * + * Return: 0 for Success, else errno. + */ +static int pcc_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mbox_controller *pcc_mbox_ctrl; + struct mbox_chan *pcc_mbox_channels; + struct acpi_table_header *pcct_tbl; + struct acpi_subtable_header *pcct_entry; + struct acpi_table_pcct *acpi_pcct_tbl; + acpi_status status = AE_OK; + int i, rc, count = pcc_chan_count; + + /* Search for PCCT */ + status = acpi_get_table(ACPI_SIG_PCCT, 0, &pcct_tbl); + + if (ACPI_FAILURE(status) || !pcct_tbl) + return -ENODEV; + + pcc_mbox_channels = devm_kcalloc(dev, count, sizeof(*pcc_mbox_channels), + GFP_KERNEL); + if (!pcc_mbox_channels) { + rc = -ENOMEM; + goto err; + } + + chan_info = devm_kcalloc(dev, count, sizeof(*chan_info), GFP_KERNEL); + if (!chan_info) { + rc = -ENOMEM; + goto err; + } + + pcc_mbox_ctrl = devm_kzalloc(dev, sizeof(*pcc_mbox_ctrl), GFP_KERNEL); + if (!pcc_mbox_ctrl) { + rc = -ENOMEM; + goto err; + } + + /* Point to the first PCC subspace entry */ + pcct_entry = (struct acpi_subtable_header *) ( + (unsigned long) pcct_tbl + sizeof(struct acpi_table_pcct)); + + acpi_pcct_tbl = (struct acpi_table_pcct *) pcct_tbl; + if (acpi_pcct_tbl->flags & ACPI_PCCT_DOORBELL) { + pcc_mbox_ctrl->txdone_irq = true; + pcc_mbox_ctrl->txdone_poll = false; + } else { + pcc_mbox_ctrl->txdone_irq = false; + pcc_mbox_ctrl->txdone_poll = true; + } + + for (i = 0; i < count; i++) { + struct pcc_chan_info *pchan = chan_info + i; + + pcc_mbox_channels[i].con_priv = pchan; + pchan->chan.mchan = &pcc_mbox_channels[i]; + + if (pcct_entry->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE && + !pcc_mbox_ctrl->txdone_irq) { + pr_err("Platform Interrupt flag must be set to 1"); + rc = -EINVAL; + goto err; + } + + if (pcc_mbox_ctrl->txdone_irq) { + rc = pcc_parse_subspace_irq(pchan, pcct_entry); + if (rc < 0) + goto err; + } + rc = pcc_parse_subspace_db_reg(pchan, pcct_entry); + if (rc < 0) + goto err; + + pcc_parse_subspace_shmem(pchan, pcct_entry); + + pchan->type = pcct_entry->type; + pcct_entry = (struct acpi_subtable_header *) + ((unsigned long) pcct_entry + pcct_entry->length); + } + + pcc_mbox_ctrl->num_chans = count; + + pr_info("Detected %d PCC Subspaces\n", pcc_mbox_ctrl->num_chans); + + pcc_mbox_ctrl->chans = pcc_mbox_channels; + pcc_mbox_ctrl->ops = &pcc_chan_ops; + pcc_mbox_ctrl->dev = dev; + + pr_info("Registering PCC driver as Mailbox controller\n"); + rc = mbox_controller_register(pcc_mbox_ctrl); + if (rc) + pr_err("Err registering PCC as Mailbox controller: %d\n", rc); + else + return 0; +err: + acpi_put_table(pcct_tbl); + return rc; +} + +static struct platform_driver pcc_mbox_driver = { + .probe = pcc_mbox_probe, + .driver = { + .name = "PCCT", + }, +}; + +static int __init pcc_init(void) +{ + int ret; + struct platform_device *pcc_pdev; + + if (acpi_disabled) + return -ENODEV; + + /* Check if PCC support is available. */ + ret = acpi_pcc_probe(); + + if (ret) { + pr_debug("ACPI PCC probe failed.\n"); + return -ENODEV; + } + + pcc_pdev = platform_create_bundle(&pcc_mbox_driver, + pcc_mbox_probe, NULL, 0, NULL, 0); + + if (IS_ERR(pcc_pdev)) { + pr_debug("Err creating PCC platform bundle\n"); + pcc_chan_count = 0; + return PTR_ERR(pcc_pdev); + } + + return 0; +} + +/* + * Make PCC init postcore so that users of this mailbox + * such as the ACPI Processor driver have it available + * at their init. + */ +postcore_initcall(pcc_init); diff --git a/drivers/mailbox/pl320-ipc.c b/drivers/mailbox/pl320-ipc.c new file mode 100644 index 000000000..606f26a2a --- /dev/null +++ b/drivers/mailbox/pl320-ipc.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2012 Calxeda, Inc. + */ +#include <linux/types.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/amba/bus.h> + +#include <linux/pl320-ipc.h> + +#define IPCMxSOURCE(m) ((m) * 0x40) +#define IPCMxDSET(m) (((m) * 0x40) + 0x004) +#define IPCMxDCLEAR(m) (((m) * 0x40) + 0x008) +#define IPCMxDSTATUS(m) (((m) * 0x40) + 0x00C) +#define IPCMxMODE(m) (((m) * 0x40) + 0x010) +#define IPCMxMSET(m) (((m) * 0x40) + 0x014) +#define IPCMxMCLEAR(m) (((m) * 0x40) + 0x018) +#define IPCMxMSTATUS(m) (((m) * 0x40) + 0x01C) +#define IPCMxSEND(m) (((m) * 0x40) + 0x020) +#define IPCMxDR(m, dr) (((m) * 0x40) + ((dr) * 4) + 0x024) + +#define IPCMMIS(irq) (((irq) * 8) + 0x800) +#define IPCMRIS(irq) (((irq) * 8) + 0x804) + +#define MBOX_MASK(n) (1 << (n)) +#define IPC_TX_MBOX 1 +#define IPC_RX_MBOX 2 + +#define CHAN_MASK(n) (1 << (n)) +#define A9_SOURCE 1 +#define M3_SOURCE 0 + +static void __iomem *ipc_base; +static int ipc_irq; +static DEFINE_MUTEX(ipc_m1_lock); +static DECLARE_COMPLETION(ipc_completion); +static ATOMIC_NOTIFIER_HEAD(ipc_notifier); + +static void __ipc_send(int mbox, u32 *data) +{ + int i; + for (i = 0; i < 7; i++) + writel_relaxed(data[i], ipc_base + IPCMxDR(mbox, i)); + writel_relaxed(0x1, ipc_base + IPCMxSEND(mbox)); +} + +static u32 __ipc_rcv(int mbox, u32 *data) +{ + int i; + for (i = 0; i < 7; i++) + data[i] = readl_relaxed(ipc_base + IPCMxDR(mbox, i)); + return data[1]; +} + +/* blocking implementation from the A9 side, not usable in interrupts! */ +int pl320_ipc_transmit(u32 *data) +{ + int ret; + + mutex_lock(&ipc_m1_lock); + + init_completion(&ipc_completion); + __ipc_send(IPC_TX_MBOX, data); + ret = wait_for_completion_timeout(&ipc_completion, + msecs_to_jiffies(1000)); + if (ret == 0) { + ret = -ETIMEDOUT; + goto out; + } + + ret = __ipc_rcv(IPC_TX_MBOX, data); +out: + mutex_unlock(&ipc_m1_lock); + return ret; +} +EXPORT_SYMBOL_GPL(pl320_ipc_transmit); + +static irqreturn_t ipc_handler(int irq, void *dev) +{ + u32 irq_stat; + u32 data[7]; + + irq_stat = readl_relaxed(ipc_base + IPCMMIS(1)); + if (irq_stat & MBOX_MASK(IPC_TX_MBOX)) { + writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX)); + complete(&ipc_completion); + } + if (irq_stat & MBOX_MASK(IPC_RX_MBOX)) { + __ipc_rcv(IPC_RX_MBOX, data); + atomic_notifier_call_chain(&ipc_notifier, data[0], data + 1); + writel_relaxed(2, ipc_base + IPCMxSEND(IPC_RX_MBOX)); + } + + return IRQ_HANDLED; +} + +int pl320_ipc_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&ipc_notifier, nb); +} +EXPORT_SYMBOL_GPL(pl320_ipc_register_notifier); + +int pl320_ipc_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&ipc_notifier, nb); +} +EXPORT_SYMBOL_GPL(pl320_ipc_unregister_notifier); + +static int pl320_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + + ipc_base = ioremap(adev->res.start, resource_size(&adev->res)); + if (ipc_base == NULL) + return -ENOMEM; + + writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX)); + + ipc_irq = adev->irq[0]; + ret = request_irq(ipc_irq, ipc_handler, 0, dev_name(&adev->dev), NULL); + if (ret < 0) + goto err; + + /* Init slow mailbox */ + writel_relaxed(CHAN_MASK(A9_SOURCE), + ipc_base + IPCMxSOURCE(IPC_TX_MBOX)); + writel_relaxed(CHAN_MASK(M3_SOURCE), + ipc_base + IPCMxDSET(IPC_TX_MBOX)); + writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE), + ipc_base + IPCMxMSET(IPC_TX_MBOX)); + + /* Init receive mailbox */ + writel_relaxed(CHAN_MASK(M3_SOURCE), + ipc_base + IPCMxSOURCE(IPC_RX_MBOX)); + writel_relaxed(CHAN_MASK(A9_SOURCE), + ipc_base + IPCMxDSET(IPC_RX_MBOX)); + writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE), + ipc_base + IPCMxMSET(IPC_RX_MBOX)); + + return 0; +err: + iounmap(ipc_base); + return ret; +} + +static const struct amba_id pl320_ids[] = { + { + .id = 0x00041320, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +static struct amba_driver pl320_driver = { + .drv = { + .name = "pl320", + }, + .id_table = pl320_ids, + .probe = pl320_probe, +}; + +static int __init ipc_init(void) +{ + return amba_driver_register(&pl320_driver); +} +subsys_initcall(ipc_init); diff --git a/drivers/mailbox/platform_mhu.c b/drivers/mailbox/platform_mhu.c new file mode 100644 index 000000000..176ce290b --- /dev/null +++ b/drivers/mailbox/platform_mhu.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 BayLibre SAS. + * Author: Neil Armstrong <narmstrong@baylibre.com> + * Synchronised with arm_mhu.c from : + * Copyright (C) 2013-2015 Fujitsu Semiconductor Ltd. + * Copyright (C) 2015 Linaro Ltd. + * Author: Jassi Brar <jaswinder.singh@linaro.org> + */ + +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mailbox_controller.h> + +#define INTR_SET_OFS 0x0 +#define INTR_STAT_OFS 0x4 +#define INTR_CLR_OFS 0x8 + +#define MHU_SEC_OFFSET 0x0 +#define MHU_LP_OFFSET 0xc +#define MHU_HP_OFFSET 0x18 +#define TX_REG_OFFSET 0x24 + +#define MHU_CHANS 3 + +struct platform_mhu_link { + int irq; + void __iomem *tx_reg; + void __iomem *rx_reg; +}; + +struct platform_mhu { + void __iomem *base; + struct platform_mhu_link mlink[MHU_CHANS]; + struct mbox_chan chan[MHU_CHANS]; + struct mbox_controller mbox; +}; + +static irqreturn_t platform_mhu_rx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct platform_mhu_link *mlink = chan->con_priv; + u32 val; + + val = readl_relaxed(mlink->rx_reg + INTR_STAT_OFS); + if (!val) + return IRQ_NONE; + + mbox_chan_received_data(chan, (void *)&val); + + writel_relaxed(val, mlink->rx_reg + INTR_CLR_OFS); + + return IRQ_HANDLED; +} + +static bool platform_mhu_last_tx_done(struct mbox_chan *chan) +{ + struct platform_mhu_link *mlink = chan->con_priv; + u32 val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS); + + return (val == 0); +} + +static int platform_mhu_send_data(struct mbox_chan *chan, void *data) +{ + struct platform_mhu_link *mlink = chan->con_priv; + u32 *arg = data; + + writel_relaxed(*arg, mlink->tx_reg + INTR_SET_OFS); + + return 0; +} + +static int platform_mhu_startup(struct mbox_chan *chan) +{ + struct platform_mhu_link *mlink = chan->con_priv; + u32 val; + int ret; + + val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS); + writel_relaxed(val, mlink->tx_reg + INTR_CLR_OFS); + + ret = request_irq(mlink->irq, platform_mhu_rx_interrupt, + IRQF_SHARED, "platform_mhu_link", chan); + if (ret) { + dev_err(chan->mbox->dev, + "Unable to acquire IRQ %d\n", mlink->irq); + return ret; + } + + return 0; +} + +static void platform_mhu_shutdown(struct mbox_chan *chan) +{ + struct platform_mhu_link *mlink = chan->con_priv; + + free_irq(mlink->irq, chan); +} + +static const struct mbox_chan_ops platform_mhu_ops = { + .send_data = platform_mhu_send_data, + .startup = platform_mhu_startup, + .shutdown = platform_mhu_shutdown, + .last_tx_done = platform_mhu_last_tx_done, +}; + +static int platform_mhu_probe(struct platform_device *pdev) +{ + int i, err; + struct platform_mhu *mhu; + struct device *dev = &pdev->dev; + int platform_mhu_reg[MHU_CHANS] = { + MHU_SEC_OFFSET, MHU_LP_OFFSET, MHU_HP_OFFSET + }; + + /* Allocate memory for device */ + mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL); + if (!mhu) + return -ENOMEM; + + mhu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mhu->base)) { + dev_err(dev, "ioremap failed\n"); + return PTR_ERR(mhu->base); + } + + for (i = 0; i < MHU_CHANS; i++) { + mhu->chan[i].con_priv = &mhu->mlink[i]; + mhu->mlink[i].irq = platform_get_irq(pdev, i); + if (mhu->mlink[i].irq < 0) + return mhu->mlink[i].irq; + mhu->mlink[i].rx_reg = mhu->base + platform_mhu_reg[i]; + mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET; + } + + mhu->mbox.dev = dev; + mhu->mbox.chans = &mhu->chan[0]; + mhu->mbox.num_chans = MHU_CHANS; + mhu->mbox.ops = &platform_mhu_ops; + mhu->mbox.txdone_irq = false; + mhu->mbox.txdone_poll = true; + mhu->mbox.txpoll_period = 1; + + platform_set_drvdata(pdev, mhu); + + err = devm_mbox_controller_register(dev, &mhu->mbox); + if (err) { + dev_err(dev, "Failed to register mailboxes %d\n", err); + return err; + } + + dev_info(dev, "Platform MHU Mailbox registered\n"); + return 0; +} + +static const struct of_device_id platform_mhu_dt_ids[] = { + { .compatible = "amlogic,meson-gxbb-mhu", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, platform_mhu_dt_ids); + +static struct platform_driver platform_mhu_driver = { + .probe = platform_mhu_probe, + .driver = { + .name = "platform-mhu", + .of_match_table = platform_mhu_dt_ids, + }, +}; + +module_platform_driver(platform_mhu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:platform-mhu"); +MODULE_DESCRIPTION("Platform MHU Driver"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); diff --git a/drivers/mailbox/qcom-apcs-ipc-mailbox.c b/drivers/mailbox/qcom-apcs-ipc-mailbox.c new file mode 100644 index 000000000..d3a8f6b4a --- /dev/null +++ b/drivers/mailbox/qcom-apcs-ipc-mailbox.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017, Linaro Ltd + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mailbox_controller.h> + +#define QCOM_APCS_IPC_BITS 32 + +struct qcom_apcs_ipc { + struct mbox_controller mbox; + struct mbox_chan mbox_chans[QCOM_APCS_IPC_BITS]; + + struct regmap *regmap; + unsigned long offset; + struct platform_device *clk; +}; + +struct qcom_apcs_ipc_data { + int offset; + char *clk_name; +}; + +static const struct qcom_apcs_ipc_data ipq6018_apcs_data = { + .offset = 8, .clk_name = "qcom,apss-ipq6018-clk" +}; + +static const struct qcom_apcs_ipc_data msm8916_apcs_data = { + .offset = 8, .clk_name = "qcom-apcs-msm8916-clk" +}; + +static const struct qcom_apcs_ipc_data msm8994_apcs_data = { + .offset = 8, .clk_name = NULL +}; + +static const struct qcom_apcs_ipc_data msm8996_apcs_data = { + .offset = 16, .clk_name = "qcom-apcs-msm8996-clk" +}; + +static const struct qcom_apcs_ipc_data apps_shared_apcs_data = { + .offset = 12, .clk_name = NULL +}; + +static const struct qcom_apcs_ipc_data sdx55_apcs_data = { + .offset = 0x1008, .clk_name = "qcom-sdx55-acps-clk" +}; + +static const struct regmap_config apcs_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1008, +}; + +static int qcom_apcs_ipc_send_data(struct mbox_chan *chan, void *data) +{ + struct qcom_apcs_ipc *apcs = container_of(chan->mbox, + struct qcom_apcs_ipc, mbox); + unsigned long idx = (unsigned long)chan->con_priv; + + return regmap_write(apcs->regmap, apcs->offset, BIT(idx)); +} + +static const struct mbox_chan_ops qcom_apcs_ipc_ops = { + .send_data = qcom_apcs_ipc_send_data, +}; + +static int qcom_apcs_ipc_probe(struct platform_device *pdev) +{ + struct qcom_apcs_ipc *apcs; + const struct qcom_apcs_ipc_data *apcs_data; + struct regmap *regmap; + void __iomem *base; + unsigned long i; + int ret; + + apcs = devm_kzalloc(&pdev->dev, sizeof(*apcs), GFP_KERNEL); + if (!apcs) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, &apcs_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + apcs_data = of_device_get_match_data(&pdev->dev); + + apcs->regmap = regmap; + apcs->offset = apcs_data->offset; + + /* Initialize channel identifiers */ + for (i = 0; i < ARRAY_SIZE(apcs->mbox_chans); i++) + apcs->mbox_chans[i].con_priv = (void *)i; + + apcs->mbox.dev = &pdev->dev; + apcs->mbox.ops = &qcom_apcs_ipc_ops; + apcs->mbox.chans = apcs->mbox_chans; + apcs->mbox.num_chans = ARRAY_SIZE(apcs->mbox_chans); + + ret = devm_mbox_controller_register(&pdev->dev, &apcs->mbox); + if (ret) { + dev_err(&pdev->dev, "failed to register APCS IPC controller\n"); + return ret; + } + + if (apcs_data->clk_name) { + struct device_node *np = of_get_child_by_name(pdev->dev.of_node, + "clock-controller"); + struct platform_device_info pdevinfo = { + .parent = &pdev->dev, + .name = apcs_data->clk_name, + .id = PLATFORM_DEVID_AUTO, + .fwnode = of_fwnode_handle(np) ?: pdev->dev.fwnode, + .of_node_reused = !np, + }; + + apcs->clk = platform_device_register_full(&pdevinfo); + of_node_put(np); + if (IS_ERR(apcs->clk)) + dev_err(&pdev->dev, "failed to register APCS clk\n"); + } + + platform_set_drvdata(pdev, apcs); + + return 0; +} + +static void qcom_apcs_ipc_remove(struct platform_device *pdev) +{ + struct qcom_apcs_ipc *apcs = platform_get_drvdata(pdev); + struct platform_device *clk = apcs->clk; + + platform_device_unregister(clk); +} + +/* .data is the offset of the ipc register within the global block */ +static const struct of_device_id qcom_apcs_ipc_of_match[] = { + { .compatible = "qcom,ipq6018-apcs-apps-global", .data = &ipq6018_apcs_data }, + { .compatible = "qcom,msm8916-apcs-kpss-global", .data = &msm8916_apcs_data }, + { .compatible = "qcom,msm8939-apcs-kpss-global", .data = &msm8916_apcs_data }, + { .compatible = "qcom,msm8953-apcs-kpss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,msm8994-apcs-kpss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,msm8996-apcs-hmss-global", .data = &msm8996_apcs_data }, + { .compatible = "qcom,qcm2290-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,sdm845-apss-shared", .data = &apps_shared_apcs_data }, + { .compatible = "qcom,sdx55-apcs-gcc", .data = &sdx55_apcs_data }, + /* Do not add any more entries using existing driver data */ + { .compatible = "qcom,msm8976-apcs-kpss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,msm8998-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,qcs404-apcs-apps-global", .data = &msm8916_apcs_data }, + { .compatible = "qcom,sdm660-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,sm4250-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,sm6125-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,sm6115-apcs-hmss-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,ipq5332-apcs-apps-global", .data = &ipq6018_apcs_data }, + { .compatible = "qcom,ipq5424-apcs-apps-global", .data = &msm8994_apcs_data }, + { .compatible = "qcom,ipq8074-apcs-apps-global", .data = &ipq6018_apcs_data }, + { .compatible = "qcom,sc7180-apss-shared", .data = &apps_shared_apcs_data }, + { .compatible = "qcom,sc8180x-apss-shared", .data = &apps_shared_apcs_data }, + { .compatible = "qcom,sm8150-apss-shared", .data = &apps_shared_apcs_data }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_apcs_ipc_of_match); + +static struct platform_driver qcom_apcs_ipc_driver = { + .probe = qcom_apcs_ipc_probe, + .remove = qcom_apcs_ipc_remove, + .driver = { + .name = "qcom_apcs_ipc", + .of_match_table = qcom_apcs_ipc_of_match, + }, +}; + +static int __init qcom_apcs_ipc_init(void) +{ + return platform_driver_register(&qcom_apcs_ipc_driver); +} +postcore_initcall(qcom_apcs_ipc_init); + +static void __exit qcom_apcs_ipc_exit(void) +{ + platform_driver_unregister(&qcom_apcs_ipc_driver); +} +module_exit(qcom_apcs_ipc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm APCS IPC driver"); diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c new file mode 100644 index 000000000..515d2d12c --- /dev/null +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define APSS_CPUCP_MBOX_CMD_OFF 0x4 + +/* Tx Registers */ +#define APSS_CPUCP_TX_MBOX_CMD(i) (0x100 + ((i) * 8)) + +/* Rx Registers */ +#define APSS_CPUCP_RX_MBOX_CMD(i) (0x100 + ((i) * 8)) +#define APSS_CPUCP_RX_MBOX_MAP 0x4000 +#define APSS_CPUCP_RX_MBOX_STAT 0x4400 +#define APSS_CPUCP_RX_MBOX_CLEAR 0x4800 +#define APSS_CPUCP_RX_MBOX_EN 0x4c00 +#define APSS_CPUCP_RX_MBOX_CMD_MASK GENMASK_ULL(63, 0) + +/** + * struct qcom_cpucp_mbox_data - Per-hardware mailbox configuration data + * @num_chans: Number of IPC channels supported by this hardware + */ +struct qcom_cpucp_mbox_data { + int num_chans; +}; + +/** + * struct qcom_cpucp_mbox - Holder for the mailbox driver + * @chans: The mailbox channel + * @mbox: The mailbox controller + * @tx_base: Base address of the CPUCP tx registers + * @rx_base: Base address of the CPUCP rx registers + */ +struct qcom_cpucp_mbox { + struct mbox_chan *chans; + struct mbox_controller mbox; + void __iomem *tx_base; + void __iomem *rx_base; +}; + +static inline int channel_number(struct mbox_chan *chan) +{ + return chan - chan->mbox->chans; +} + +static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data) +{ + struct qcom_cpucp_mbox *cpucp = data; + u64 status; + int i; + + status = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_STAT); + + for_each_set_bit(i, (unsigned long *)&status, cpucp->mbox.num_chans) { + u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF); + struct mbox_chan *chan = &cpucp->chans[i]; + struct mbox_client *cl; + unsigned long flags; + + /* + * Provide mutual exclusion with changes to chan->cl. + * Save cl locally and clear the HW interrupt inside the lock, + * then invoke mbox_chan_received_data() outside the lock to + * avoid a PREEMPT_RT self-deadlock: mbox_chan_received_data() + * can call back into mbox_send_message() via scmi_rx_callback() + * -> mailbox_clear_channel(), which re-acquires chan->lock + * (converted to an rt_spinlock under PREEMPT_RT). + */ + spin_lock_irqsave(&chan->lock, flags); + cl = chan->cl; + writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); + spin_unlock_irqrestore(&chan->lock, flags); + + if (cl) + mbox_chan_received_data(chan, &val); + } + + return IRQ_HANDLED; +} + +static int qcom_cpucp_mbox_startup(struct mbox_chan *chan) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u64 val; + + val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + val |= BIT(chan_id); + writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + + return 0; +} + +static void qcom_cpucp_mbox_shutdown(struct mbox_chan *chan) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u64 val; + + val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + val &= ~BIT(chan_id); + writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); +} + +static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u32 *val = data; + + /* + * mailbox_clear_channel() calls mbox_send_message() with NULL data to + * signal the remote side that the channel has been cleared. Nothing + * needs to be written to the TX register in that case, so just return. + */ + if (!val) + return 0; + + writel(*val, cpucp->tx_base + APSS_CPUCP_TX_MBOX_CMD(chan_id) + APSS_CPUCP_MBOX_CMD_OFF); + + return 0; +} + +static const struct mbox_chan_ops qcom_cpucp_mbox_chan_ops = { + .startup = qcom_cpucp_mbox_startup, + .send_data = qcom_cpucp_mbox_send_data, + .shutdown = qcom_cpucp_mbox_shutdown +}; + +static int qcom_cpucp_mbox_probe(struct platform_device *pdev) +{ + const struct qcom_cpucp_mbox_data *data; + struct device *dev = &pdev->dev; + struct qcom_cpucp_mbox *cpucp; + struct mbox_controller *mbox; + int irq, ret; + + data = of_device_get_match_data(dev); + if (!data) + return dev_err_probe(dev, -EINVAL, "No match data found\n"); + + cpucp = devm_kzalloc(dev, sizeof(*cpucp), GFP_KERNEL); + if (!cpucp) + return -ENOMEM; + + cpucp->chans = devm_kcalloc(dev, data->num_chans, sizeof(*cpucp->chans), GFP_KERNEL); + if (!cpucp->chans) + return -ENOMEM; + + cpucp->rx_base = devm_of_iomap(dev, dev->of_node, 0, NULL); + if (IS_ERR(cpucp->rx_base)) + return PTR_ERR(cpucp->rx_base); + + cpucp->tx_base = devm_of_iomap(dev, dev->of_node, 1, NULL); + if (IS_ERR(cpucp->tx_base)) + return PTR_ERR(cpucp->tx_base); + + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, qcom_cpucp_mbox_irq_fn, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "apss_cpucp_mbox", cpucp); + if (ret < 0) + return ret; + + writeq(APSS_CPUCP_RX_MBOX_CMD_MASK, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); + + mbox = &cpucp->mbox; + mbox->dev = dev; + mbox->num_chans = data->num_chans; + mbox->chans = cpucp->chans; + mbox->ops = &qcom_cpucp_mbox_chan_ops; + + ret = devm_mbox_controller_register(dev, mbox); + if (ret) + return dev_err_probe(dev, ret, "Failed to create mailbox\n"); + + return 0; +} + +static const struct qcom_cpucp_mbox_data qcom_x1e80100_mbox_data = { + .num_chans = 3, +}; + +static const struct qcom_cpucp_mbox_data qcom_nord_mbox_data = { + .num_chans = 16, +}; + +static const struct of_device_id qcom_cpucp_mbox_of_match[] = { + { .compatible = "qcom,nord-cpucp-mbox", .data = &qcom_nord_mbox_data }, + { .compatible = "qcom,x1e80100-cpucp-mbox", .data = &qcom_x1e80100_mbox_data }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_cpucp_mbox_of_match); + +static struct platform_driver qcom_cpucp_mbox_driver = { + .probe = qcom_cpucp_mbox_probe, + .driver = { + .name = "qcom_cpucp_mbox", + .of_match_table = qcom_cpucp_mbox_of_match, + }, +}; + +static int __init qcom_cpucp_mbox_init(void) +{ + return platform_driver_register(&qcom_cpucp_mbox_driver); +} +core_initcall(qcom_cpucp_mbox_init); + +static void __exit qcom_cpucp_mbox_exit(void) +{ + platform_driver_unregister(&qcom_cpucp_mbox_driver); +} +module_exit(qcom_cpucp_mbox_exit); + +MODULE_DESCRIPTION("QTI CPUCP MBOX Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c new file mode 100644 index 000000000..185b63f72 --- /dev/null +++ b/drivers/mailbox/qcom-ipcc.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <dt-bindings/mailbox/qcom-ipcc.h> + +/* IPCC Register offsets */ +#define IPCC_REG_CONFIG 0x08 +#define IPCC_REG_SEND_ID 0x0c +#define IPCC_REG_RECV_ID 0x10 +#define IPCC_REG_RECV_SIGNAL_ENABLE 0x14 +#define IPCC_REG_RECV_SIGNAL_DISABLE 0x18 +#define IPCC_REG_RECV_SIGNAL_CLEAR 0x1c +#define IPCC_REG_CLIENT_CLEAR 0x38 + +#define IPCC_CLEAR_ON_RECV_RD BIT(0) +#define IPCC_SIGNAL_ID_MASK GENMASK(15, 0) +#define IPCC_CLIENT_ID_MASK GENMASK(31, 16) + +#define IPCC_NO_PENDING_IRQ GENMASK(31, 0) + +/** + * struct qcom_ipcc_chan_info - Per-mailbox-channel info + * @client_id: The client-id to which the interrupt has to be triggered + * @signal_id: The signal-id to which the interrupt has to be triggered + */ +struct qcom_ipcc_chan_info { + u16 client_id; + u16 signal_id; +}; + +/** + * struct qcom_ipcc - Holder for the mailbox driver + * @dev: Device associated with this instance + * @base: Base address of the IPCC frame associated to APSS + * @irq_domain: The irq_domain associated with this instance + * @chans: The mailbox channels array + * @mchan: The per-mailbox channel info array + * @mbox: The mailbox controller + * @num_chans: Number of @chans elements + * @irq: Summary irq + */ +struct qcom_ipcc { + struct device *dev; + void __iomem *base; + struct irq_domain *irq_domain; + struct mbox_chan *chans; + struct qcom_ipcc_chan_info *mchan; + struct mbox_controller mbox; + int num_chans; + int irq; +}; + +static inline struct qcom_ipcc *to_qcom_ipcc(struct mbox_controller *mbox) +{ + return container_of(mbox, struct qcom_ipcc, mbox); +} + +static inline u32 qcom_ipcc_get_hwirq(u16 client_id, u16 signal_id) +{ + return FIELD_PREP(IPCC_CLIENT_ID_MASK, client_id) | + FIELD_PREP(IPCC_SIGNAL_ID_MASK, signal_id); +} + +static irqreturn_t qcom_ipcc_irq_fn(int irq, void *data) +{ + struct qcom_ipcc *ipcc = data; + u32 hwirq; + int virq; + + for (;;) { + hwirq = readl(ipcc->base + IPCC_REG_RECV_ID); + if (hwirq == IPCC_NO_PENDING_IRQ) + break; + + virq = irq_find_mapping(ipcc->irq_domain, hwirq); + writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_CLEAR); + generic_handle_irq(virq); + } + + return IRQ_HANDLED; +} + +static void qcom_ipcc_mask_irq(struct irq_data *irqd) +{ + struct qcom_ipcc *ipcc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_DISABLE); +} + +static void qcom_ipcc_unmask_irq(struct irq_data *irqd) +{ + struct qcom_ipcc *ipcc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_ENABLE); +} + +static struct irq_chip qcom_ipcc_irq_chip = { + .name = "ipcc", + .irq_mask = qcom_ipcc_mask_irq, + .irq_unmask = qcom_ipcc_unmask_irq, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static int qcom_ipcc_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct qcom_ipcc *ipcc = d->host_data; + + irq_set_chip_and_handler(irq, &qcom_ipcc_irq_chip, handle_level_irq); + irq_set_chip_data(irq, ipcc); + irq_set_noprobe(irq); + + return 0; +} + +static int qcom_ipcc_domain_xlate(struct irq_domain *d, + struct device_node *node, const u32 *intspec, + unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + *out_hwirq = qcom_ipcc_get_hwirq(intspec[0], intspec[1]); + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static const struct irq_domain_ops qcom_ipcc_irq_ops = { + .map = qcom_ipcc_domain_map, + .xlate = qcom_ipcc_domain_xlate, +}; + +static int qcom_ipcc_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct qcom_ipcc *ipcc = to_qcom_ipcc(chan->mbox); + struct qcom_ipcc_chan_info *mchan = chan->con_priv; + u32 hwirq; + + hwirq = qcom_ipcc_get_hwirq(mchan->client_id, mchan->signal_id); + writel(hwirq, ipcc->base + IPCC_REG_SEND_ID); + + return 0; +} + +static void qcom_ipcc_mbox_shutdown(struct mbox_chan *chan) +{ + chan->con_priv = NULL; +} + +static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *ph) +{ + struct qcom_ipcc *ipcc = to_qcom_ipcc(mbox); + struct qcom_ipcc_chan_info *mchan; + struct mbox_chan *chan, *free_chan = NULL; + struct device *dev; + int chan_id; + + dev = ipcc->dev; + + if (ph->args_count != 2) + return ERR_PTR(-EINVAL); + + for (chan_id = 0; chan_id < mbox->num_chans; chan_id++) { + chan = &ipcc->chans[chan_id]; + mchan = chan->con_priv; + + if (!mchan) { + /* Keep scanning past holes to reject duplicate channel requests. */ + if (!free_chan) + free_chan = chan; + } else if (mchan->client_id == ph->args[0] && + mchan->signal_id == ph->args[1]) { + return ERR_PTR(-EBUSY); + } + } + + if (!free_chan) + return ERR_PTR(-EBUSY); + + chan = free_chan; + + mchan = devm_kzalloc(dev, sizeof(*mchan), GFP_KERNEL); + if (!mchan) + return ERR_PTR(-ENOMEM); + + mchan->client_id = ph->args[0]; + mchan->signal_id = ph->args[1]; + chan->con_priv = mchan; + + return chan; +} + +static const struct mbox_chan_ops ipcc_mbox_chan_ops = { + .send_data = qcom_ipcc_mbox_send_data, + .shutdown = qcom_ipcc_mbox_shutdown, +}; + +static int qcom_ipcc_setup_mbox(struct qcom_ipcc *ipcc, + struct device_node *controller_dn) +{ + struct of_phandle_args curr_ph; + struct device_node *client_dn; + struct mbox_controller *mbox; + struct device *dev = ipcc->dev; + int i, j, ret; + + /* + * Find out the number of clients interested in this mailbox + * and create channels accordingly. + */ + ipcc->num_chans = 0; + for_each_node_with_property(client_dn, "mboxes") { + if (!of_device_is_available(client_dn)) + continue; + i = of_count_phandle_with_args(client_dn, + "mboxes", "#mbox-cells"); + for (j = 0; j < i; j++) { + ret = of_parse_phandle_with_args(client_dn, "mboxes", + "#mbox-cells", j, &curr_ph); + of_node_put(curr_ph.np); + if (!ret && curr_ph.np == controller_dn) + ipcc->num_chans++; + } + } + + /* If no clients are found, skip registering as a mbox controller */ + if (!ipcc->num_chans) + return 0; + + ipcc->chans = devm_kcalloc(dev, ipcc->num_chans, + sizeof(struct mbox_chan), GFP_KERNEL); + if (!ipcc->chans) + return -ENOMEM; + + mbox = &ipcc->mbox; + mbox->dev = dev; + mbox->num_chans = ipcc->num_chans; + mbox->chans = ipcc->chans; + mbox->ops = &ipcc_mbox_chan_ops; + mbox->of_xlate = qcom_ipcc_mbox_xlate; + mbox->txdone_irq = false; + mbox->txdone_poll = false; + + return devm_mbox_controller_register(dev, mbox); +} + +static int qcom_ipcc_pm_resume(struct device *dev) +{ + struct qcom_ipcc *ipcc = dev_get_drvdata(dev); + u32 hwirq; + int virq; + + hwirq = readl(ipcc->base + IPCC_REG_RECV_ID); + if (hwirq == IPCC_NO_PENDING_IRQ) + return 0; + + virq = irq_find_mapping(ipcc->irq_domain, hwirq); + + dev_dbg(dev, "virq: %d triggered client-id: %ld; signal-id: %ld\n", virq, + FIELD_GET(IPCC_CLIENT_ID_MASK, hwirq), FIELD_GET(IPCC_SIGNAL_ID_MASK, hwirq)); + + return 0; +} + +static int qcom_ipcc_probe(struct platform_device *pdev) +{ + struct qcom_ipcc *ipcc; + u32 config_value; + static int id; + char *name; + int ret; + + ipcc = devm_kzalloc(&pdev->dev, sizeof(*ipcc), GFP_KERNEL); + if (!ipcc) + return -ENOMEM; + + ipcc->dev = &pdev->dev; + + ipcc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ipcc->base)) + return PTR_ERR(ipcc->base); + + /* + * It is possible that boot firmware is using the same IPCC instance + * as of the HLOS and it has kept CLEAR_ON_RECV_RD set which basically + * means Interrupt pending registers are cleared when RECV_ID is read. + * The register automatically updates to the next pending interrupt/client + * status based on priority. + */ + config_value = readl(ipcc->base + IPCC_REG_CONFIG); + if (config_value & IPCC_CLEAR_ON_RECV_RD) { + config_value &= ~(IPCC_CLEAR_ON_RECV_RD); + writel(config_value, ipcc->base + IPCC_REG_CONFIG); + } + + ipcc->irq = platform_get_irq(pdev, 0); + if (ipcc->irq < 0) + return ipcc->irq; + + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ipcc_%d", id++); + if (!name) + return -ENOMEM; + + ipcc->irq_domain = irq_domain_create_tree(dev_fwnode(&pdev->dev), &qcom_ipcc_irq_ops, ipcc); + if (!ipcc->irq_domain) + return -ENOMEM; + + ret = qcom_ipcc_setup_mbox(ipcc, pdev->dev.of_node); + if (ret) + goto err_mbox; + + ret = devm_request_irq(&pdev->dev, ipcc->irq, qcom_ipcc_irq_fn, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | + IRQF_NO_THREAD, name, ipcc); + if (ret < 0) + goto err_req_irq; + + platform_set_drvdata(pdev, ipcc); + + return 0; + +err_req_irq: + if (ipcc->num_chans) + mbox_controller_unregister(&ipcc->mbox); +err_mbox: + irq_domain_remove(ipcc->irq_domain); + + return ret; +} + +static void qcom_ipcc_remove(struct platform_device *pdev) +{ + struct qcom_ipcc *ipcc = platform_get_drvdata(pdev); + + disable_irq_wake(ipcc->irq); + irq_domain_remove(ipcc->irq_domain); +} + +static const struct of_device_id qcom_ipcc_of_match[] = { + { .compatible = "qcom,ipcc"}, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_ipcc_of_match); + +static const struct dev_pm_ops qcom_ipcc_dev_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, qcom_ipcc_pm_resume) +}; + +static struct platform_driver qcom_ipcc_driver = { + .probe = qcom_ipcc_probe, + .remove = qcom_ipcc_remove, + .driver = { + .name = "qcom-ipcc", + .of_match_table = qcom_ipcc_of_match, + .suppress_bind_attrs = true, + .pm = pm_sleep_ptr(&qcom_ipcc_dev_pm_ops), + }, +}; + +static int __init qcom_ipcc_init(void) +{ + return platform_driver_register(&qcom_ipcc_driver); +} +arch_initcall(qcom_ipcc_init); + +MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vnkgutta@codeaurora.org>"); +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. IPCC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c new file mode 100644 index 000000000..ea69c6b6b --- /dev/null +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RISC-V SBI Message Proxy (MPXY) mailbox controller driver + * + * Copyright (C) 2025 Ventana Micro Systems Inc. + */ + +#include <linux/acpi.h> +#include <linux/cpu.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/irqchip/riscv-imsic.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/riscv-rpmi-message.h> +#include <linux/minmax.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of_irq.h> +#include <linux/percpu.h> +#include <linux/platform_device.h> +#include <linux/smp.h> +#include <linux/string.h> +#include <linux/types.h> +#include <asm/byteorder.h> +#include <asm/sbi.h> + +/* ====== SBI MPXY extension data structures ====== */ + +/* SBI MPXY MSI related channel attributes */ +struct sbi_mpxy_msi_info { + /* Lower 32-bits of the MSI target address */ + u32 msi_addr_lo; + /* Upper 32-bits of the MSI target address */ + u32 msi_addr_hi; + /* MSI data value */ + u32 msi_data; +}; + +/* + * SBI MPXY standard channel attributes. + * + * NOTE: The sequence of attribute fields are as-per the + * defined sequence in the attribute table in spec (or + * as-per the enum sbi_mpxy_attribute_id). + */ +struct sbi_mpxy_channel_attrs { + /* Message protocol ID */ + u32 msg_proto_id; + /* Message protocol version */ + u32 msg_proto_version; + /* Message protocol maximum message length */ + u32 msg_max_len; + /* Message protocol message send timeout in microseconds */ + u32 msg_send_timeout; + /* Message protocol message completion timeout in microseconds */ + u32 msg_completion_timeout; + /* Bit array for channel capabilities */ + u32 capability; + /* SSE event ID */ + u32 sse_event_id; + /* MSI enable/disable control knob */ + u32 msi_control; + /* Channel MSI info */ + struct sbi_mpxy_msi_info msi_info; + /* Events state control */ + u32 events_state_ctrl; +}; + +/* + * RPMI specific SBI MPXY channel attributes. + * + * NOTE: The sequence of attribute fields are as-per the + * defined sequence in the attribute table in spec (or + * as-per the enum sbi_mpxy_rpmi_attribute_id). + */ +struct sbi_mpxy_rpmi_channel_attrs { + /* RPMI service group ID */ + u32 servicegroup_id; + /* RPMI service group version */ + u32 servicegroup_version; + /* RPMI implementation ID */ + u32 impl_id; + /* RPMI implementation version */ + u32 impl_version; +}; + +/* SBI MPXY channel IDs data in shared memory */ +struct sbi_mpxy_channel_ids_data { + /* Remaining number of channel ids */ + __le32 remaining; + /* Returned channel ids in current function call */ + __le32 returned; + /* Returned channel id array */ + __le32 channel_array[]; +}; + +/* SBI MPXY notification data in shared memory */ +struct sbi_mpxy_notification_data { + /* Remaining number of notification events */ + __le32 remaining; + /* Number of notification events returned */ + __le32 returned; + /* Number of notification events lost */ + __le32 lost; + /* Reserved for future use */ + __le32 reserved; + /* Returned channel id array */ + u8 events_data[]; +}; + +/* ====== MPXY data structures & helper routines ====== */ + +/* MPXY Per-CPU or local context */ +struct mpxy_local { + /* Shared memory base address */ + void *shmem; + /* Shared memory physical address */ + phys_addr_t shmem_phys_addr; + /* Flag representing whether shared memory is active or not */ + bool shmem_active; +}; + +static DEFINE_PER_CPU(struct mpxy_local, mpxy_local); +static unsigned long mpxy_shmem_size; +static bool mpxy_shmem_init_done; + +static int mpxy_get_channel_count(u32 *channel_count) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem; + u32 remaining, returned; + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!channel_count) + return -EINVAL; + + get_cpu(); + + /* Get the remaining and returned fields to calculate total */ + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS, + 0, 0, 0, 0, 0, 0); + if (sret.error) + goto err_put_cpu; + + remaining = le32_to_cpu(sdata->remaining); + returned = le32_to_cpu(sdata->returned); + *channel_count = remaining + returned; + +err_put_cpu: + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_get_channel_ids(u32 channel_count, u32 *channel_ids) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem; + u32 remaining, returned, count, start_index = 0; + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!channel_count || !channel_ids) + return -EINVAL; + + get_cpu(); + + do { + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS, + start_index, 0, 0, 0, 0, 0); + if (sret.error) + goto err_put_cpu; + + remaining = le32_to_cpu(sdata->remaining); + returned = le32_to_cpu(sdata->returned); + + count = returned < (channel_count - start_index) ? + returned : (channel_count - start_index); + memcpy_from_le32(&channel_ids[start_index], sdata->channel_array, count); + start_index += count; + } while (remaining && start_index < channel_count); + +err_put_cpu: + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_read_attrs(u32 channel_id, u32 base_attrid, u32 attr_count, + u32 *attrs_buf) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!attr_count || !attrs_buf) + return -EINVAL; + + get_cpu(); + + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_READ_ATTRS, + channel_id, base_attrid, attr_count, 0, 0, 0); + if (sret.error) + goto err_put_cpu; + + memcpy_from_le32(attrs_buf, (__le32 *)mpxy->shmem, attr_count); + +err_put_cpu: + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_write_attrs(u32 channel_id, u32 base_attrid, u32 attr_count, + u32 *attrs_buf) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!attr_count || !attrs_buf) + return -EINVAL; + + get_cpu(); + + memcpy_to_le32((__le32 *)mpxy->shmem, attrs_buf, attr_count); + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_WRITE_ATTRS, + channel_id, base_attrid, attr_count, 0, 0, 0); + + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_send_message_with_resp(u32 channel_id, u32 msg_id, + void *tx, unsigned long tx_len, + void *rx, unsigned long max_rx_len, + unsigned long *rx_len) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + unsigned long rx_bytes; + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!tx && tx_len) + return -EINVAL; + + get_cpu(); + + /* Message protocols allowed to have no data in messages */ + if (tx_len) + memcpy(mpxy->shmem, tx, tx_len); + + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SEND_MSG_WITH_RESP, + channel_id, msg_id, tx_len, 0, 0, 0); + if (rx && !sret.error) { + rx_bytes = sret.value; + if (rx_bytes > max_rx_len) { + put_cpu(); + return -ENOSPC; + } + + memcpy(rx, mpxy->shmem, rx_bytes); + if (rx_len) + *rx_len = rx_bytes; + } + + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_send_message_without_resp(u32 channel_id, u32 msg_id, + void *tx, unsigned long tx_len) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!tx && tx_len) + return -EINVAL; + + get_cpu(); + + /* Message protocols allowed to have no data in messages */ + if (tx_len) + memcpy(mpxy->shmem, tx, tx_len); + + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SEND_MSG_WITHOUT_RESP, + channel_id, msg_id, tx_len, 0, 0, 0); + + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_get_notifications(u32 channel_id, + struct sbi_mpxy_notification_data *notif_data, + unsigned long *events_data_len) +{ + struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct sbiret sret; + + if (!mpxy->shmem_active) + return -ENODEV; + if (!notif_data || !events_data_len) + return -EINVAL; + + get_cpu(); + + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_NOTIFICATION_EVENTS, + channel_id, 0, 0, 0, 0, 0); + if (sret.error) + goto err_put_cpu; + if (sret.value < 0 || mpxy_shmem_size < sizeof(*notif_data) || + sret.value > mpxy_shmem_size - sizeof(*notif_data)) { + put_cpu(); + return -EOVERFLOW; + } + + memcpy(notif_data, mpxy->shmem, sret.value + sizeof(*notif_data)); + *events_data_len = sret.value; + +err_put_cpu: + put_cpu(); + return sbi_err_map_linux_errno(sret.error); +} + +static int mpxy_get_shmem_size(unsigned long *shmem_size) +{ + struct sbiret sret; + + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_SHMEM_SIZE, + 0, 0, 0, 0, 0, 0); + if (sret.error) + return sbi_err_map_linux_errno(sret.error); + if (shmem_size) + *shmem_size = sret.value; + return 0; +} + +static int mpxy_setup_shmem(unsigned int cpu) +{ + struct page *shmem_page; + struct mpxy_local *mpxy; + struct sbiret sret; + + mpxy = per_cpu_ptr(&mpxy_local, cpu); + if (mpxy->shmem_active) + return 0; + + shmem_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, get_order(mpxy_shmem_size)); + if (!shmem_page) + return -ENOMEM; + + /* + * Linux setup of shmem is done in mpxy OVERWRITE mode. + * flags[1:0] = 00b + */ + sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SET_SHMEM, + page_to_phys(shmem_page), 0, 0, 0, 0, 0); + if (sret.error) { + free_pages((unsigned long)page_to_virt(shmem_page), + get_order(mpxy_shmem_size)); + return sbi_err_map_linux_errno(sret.error); + } + + mpxy->shmem = page_to_virt(shmem_page); + mpxy->shmem_phys_addr = page_to_phys(shmem_page); + mpxy->shmem_active = true; + + return 0; +} + +/* ====== MPXY mailbox data structures ====== */ + +/* MPXY mailbox channel */ +struct mpxy_mbox_channel { + struct mpxy_mbox *mbox; + u32 channel_id; + struct sbi_mpxy_channel_attrs attrs; + struct sbi_mpxy_rpmi_channel_attrs rpmi_attrs; + struct sbi_mpxy_notification_data *notif; + u32 max_xfer_len; + bool have_events_state; + u32 msi_index; + u32 msi_irq; + bool started; +}; + +/* MPXY mailbox */ +struct mpxy_mbox { + struct device *dev; + u32 channel_count; + struct mpxy_mbox_channel *channels; + u32 msi_count; + struct mpxy_mbox_channel **msi_index_to_channel; + struct mbox_controller controller; +}; + +/* ====== MPXY RPMI processing ====== */ + +static void mpxy_mbox_send_rpmi_data(struct mpxy_mbox_channel *mchan, + struct rpmi_mbox_message *msg) +{ + msg->error = 0; + switch (msg->type) { + case RPMI_MBOX_MSG_TYPE_GET_ATTRIBUTE: + switch (msg->attr.id) { + case RPMI_MBOX_ATTR_SPEC_VERSION: + msg->attr.value = mchan->attrs.msg_proto_version; + break; + case RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE: + msg->attr.value = mchan->max_xfer_len; + break; + case RPMI_MBOX_ATTR_SERVICEGROUP_ID: + msg->attr.value = mchan->rpmi_attrs.servicegroup_id; + break; + case RPMI_MBOX_ATTR_SERVICEGROUP_VERSION: + msg->attr.value = mchan->rpmi_attrs.servicegroup_version; + break; + case RPMI_MBOX_ATTR_IMPL_ID: + msg->attr.value = mchan->rpmi_attrs.impl_id; + break; + case RPMI_MBOX_ATTR_IMPL_VERSION: + msg->attr.value = mchan->rpmi_attrs.impl_version; + break; + default: + msg->error = -EOPNOTSUPP; + break; + } + break; + case RPMI_MBOX_MSG_TYPE_SET_ATTRIBUTE: + /* None of the RPMI linux mailbox attributes are writeable */ + msg->error = -EOPNOTSUPP; + break; + case RPMI_MBOX_MSG_TYPE_SEND_WITH_RESPONSE: + if ((!msg->data.request && msg->data.request_len) || + (msg->data.request && msg->data.request_len > mchan->max_xfer_len) || + (!msg->data.response && msg->data.max_response_len)) { + msg->error = -EINVAL; + break; + } + if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_SEND_WITH_RESP)) { + msg->error = -EIO; + break; + } + msg->error = mpxy_send_message_with_resp(mchan->channel_id, + msg->data.service_id, + msg->data.request, + msg->data.request_len, + msg->data.response, + msg->data.max_response_len, + &msg->data.out_response_len); + break; + case RPMI_MBOX_MSG_TYPE_SEND_WITHOUT_RESPONSE: + if ((!msg->data.request && msg->data.request_len) || + (msg->data.request && msg->data.request_len > mchan->max_xfer_len)) { + msg->error = -EINVAL; + break; + } + if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_SEND_WITHOUT_RESP)) { + msg->error = -EIO; + break; + } + msg->error = mpxy_send_message_without_resp(mchan->channel_id, + msg->data.service_id, + msg->data.request, + msg->data.request_len); + break; + default: + msg->error = -EOPNOTSUPP; + break; + } +} + +static void mpxy_mbox_peek_rpmi_data(struct mbox_chan *chan, + struct mpxy_mbox_channel *mchan, + struct sbi_mpxy_notification_data *notif, + unsigned long events_data_len) +{ + struct rpmi_notification_event *event; + struct rpmi_mbox_message msg; + unsigned long pos = 0; + + while (events_data_len - pos >= sizeof(*event)) { + event = (struct rpmi_notification_event *)(notif->events_data + pos); + + msg.type = RPMI_MBOX_MSG_TYPE_NOTIFICATION_EVENT; + msg.notif.event_datalen = le16_to_cpu(event->event_datalen); + if (msg.notif.event_datalen > + events_data_len - pos - sizeof(*event)) + break; + msg.notif.event_id = event->event_id; + msg.notif.event_data = event->event_data; + msg.error = 0; + + mbox_chan_received_data(chan, &msg); + pos += sizeof(*event) + msg.notif.event_datalen; + } +} + +static int mpxy_mbox_read_rpmi_attrs(struct mpxy_mbox_channel *mchan) +{ + return mpxy_read_attrs(mchan->channel_id, + SBI_MPXY_ATTR_MSGPROTO_ATTR_START, + sizeof(mchan->rpmi_attrs) / sizeof(u32), + (u32 *)&mchan->rpmi_attrs); +} + +/* ====== MPXY mailbox callbacks ====== */ + +static int mpxy_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct mpxy_mbox_channel *mchan = chan->con_priv; + + if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) { + mpxy_mbox_send_rpmi_data(mchan, data); + return 0; + } + + return -EOPNOTSUPP; +} + +static bool mpxy_mbox_peek_data(struct mbox_chan *chan) +{ + struct mpxy_mbox_channel *mchan = chan->con_priv; + struct sbi_mpxy_notification_data *notif = mchan->notif; + bool have_notifications = false; + unsigned long data_len; + int rc; + + if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS)) + return false; + + do { + rc = mpxy_get_notifications(mchan->channel_id, notif, &data_len); + if (rc || !data_len) + break; + + if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) + mpxy_mbox_peek_rpmi_data(chan, mchan, notif, data_len); + + have_notifications = true; + } while (1); + + return have_notifications; +} + +static irqreturn_t mpxy_mbox_irq_thread(int irq, void *dev_id) +{ + mpxy_mbox_peek_data(dev_id); + return IRQ_HANDLED; +} + +static int mpxy_mbox_setup_msi(struct mbox_chan *chan, + struct mpxy_mbox_channel *mchan) +{ + struct device *dev = mchan->mbox->dev; + int rc; + + /* Do nothing if MSI not supported */ + if (mchan->msi_irq == U32_MAX) + return 0; + + /* Fail if MSI already enabled */ + if (mchan->attrs.msi_control) + return -EALREADY; + + /* Request channel MSI handler */ + rc = request_threaded_irq(mchan->msi_irq, NULL, mpxy_mbox_irq_thread, + 0, dev_name(dev), chan); + if (rc) { + dev_err(dev, "failed to request MPXY channel 0x%x IRQ\n", + mchan->channel_id); + return rc; + } + + /* Enable channel MSI control */ + mchan->attrs.msi_control = 1; + rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_CONTROL, + 1, &mchan->attrs.msi_control); + if (rc) { + dev_err(dev, "enable MSI control failed for MPXY channel 0x%x\n", + mchan->channel_id); + mchan->attrs.msi_control = 0; + free_irq(mchan->msi_irq, chan); + return rc; + } + + return 0; +} + +static void mpxy_mbox_cleanup_msi(struct mbox_chan *chan, + struct mpxy_mbox_channel *mchan) +{ + struct device *dev = mchan->mbox->dev; + int rc; + + /* Do nothing if MSI not supported */ + if (mchan->msi_irq == U32_MAX) + return; + + /* Do nothing if MSI already disabled */ + if (!mchan->attrs.msi_control) + return; + + /* Disable channel MSI control */ + mchan->attrs.msi_control = 0; + rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_CONTROL, + 1, &mchan->attrs.msi_control); + if (rc) { + dev_err(dev, "disable MSI control failed for MPXY channel 0x%x\n", + mchan->channel_id); + } + + /* Free channel MSI handler */ + free_irq(mchan->msi_irq, chan); +} + +static int mpxy_mbox_setup_events(struct mpxy_mbox_channel *mchan) +{ + struct device *dev = mchan->mbox->dev; + int rc; + + /* Do nothing if events state not supported */ + if (!mchan->have_events_state) + return 0; + + /* Fail if events state already enabled */ + if (mchan->attrs.events_state_ctrl) + return -EALREADY; + + /* Enable channel events state */ + mchan->attrs.events_state_ctrl = 1; + rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_EVENTS_STATE_CONTROL, + 1, &mchan->attrs.events_state_ctrl); + if (rc) { + dev_err(dev, "enable events state failed for MPXY channel 0x%x\n", + mchan->channel_id); + mchan->attrs.events_state_ctrl = 0; + return rc; + } + + return 0; +} + +static void mpxy_mbox_cleanup_events(struct mpxy_mbox_channel *mchan) +{ + struct device *dev = mchan->mbox->dev; + int rc; + + /* Do nothing if events state not supported */ + if (!mchan->have_events_state) + return; + + /* Do nothing if events state already disabled */ + if (!mchan->attrs.events_state_ctrl) + return; + + /* Disable channel events state */ + mchan->attrs.events_state_ctrl = 0; + rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_EVENTS_STATE_CONTROL, + 1, &mchan->attrs.events_state_ctrl); + if (rc) + dev_err(dev, "disable events state failed for MPXY channel 0x%x\n", + mchan->channel_id); +} + +static int mpxy_mbox_startup(struct mbox_chan *chan) +{ + struct mpxy_mbox_channel *mchan = chan->con_priv; + int rc; + + if (mchan->started) + return -EALREADY; + + /* Setup channel MSI */ + rc = mpxy_mbox_setup_msi(chan, mchan); + if (rc) + return rc; + + /* Setup channel notification events */ + rc = mpxy_mbox_setup_events(mchan); + if (rc) { + mpxy_mbox_cleanup_msi(chan, mchan); + return rc; + } + + /* Mark the channel as started */ + mchan->started = true; + + return 0; +} + +static void mpxy_mbox_shutdown(struct mbox_chan *chan) +{ + struct mpxy_mbox_channel *mchan = chan->con_priv; + + if (!mchan->started) + return; + + /* Mark the channel as stopped */ + mchan->started = false; + + /* Cleanup channel notification events */ + mpxy_mbox_cleanup_events(mchan); + + /* Cleanup channel MSI */ + mpxy_mbox_cleanup_msi(chan, mchan); +} + +static const struct mbox_chan_ops mpxy_mbox_ops = { + .send_data = mpxy_mbox_send_data, + .peek_data = mpxy_mbox_peek_data, + .startup = mpxy_mbox_startup, + .shutdown = mpxy_mbox_shutdown, +}; + +/* ====== MPXY platform driver ===== */ + +static void mpxy_mbox_msi_write(struct msi_desc *desc, struct msi_msg *msg) +{ + struct device *dev = msi_desc_to_dev(desc); + struct mpxy_mbox *mbox = dev_get_drvdata(dev); + struct mpxy_mbox_channel *mchan; + struct sbi_mpxy_msi_info *minfo; + int rc; + + mchan = mbox->msi_index_to_channel[desc->msi_index]; + if (!mchan) { + dev_warn(dev, "MPXY channel not available for MSI index %d\n", + desc->msi_index); + return; + } + + minfo = &mchan->attrs.msi_info; + minfo->msi_addr_lo = msg->address_lo; + minfo->msi_addr_hi = msg->address_hi; + minfo->msi_data = msg->data; + + rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_ADDR_LO, + sizeof(*minfo) / sizeof(u32), (u32 *)minfo); + if (rc) { + dev_warn(dev, "failed to write MSI info for MPXY channel 0x%x\n", + mchan->channel_id); + } +} + +static struct mbox_chan *mpxy_mbox_fw_xlate(struct mbox_controller *ctlr, + const struct fwnode_reference_args *pa) +{ + struct mpxy_mbox *mbox = container_of(ctlr, struct mpxy_mbox, controller); + struct mpxy_mbox_channel *mchan; + u32 i; + + if (pa->nargs != 2) + return ERR_PTR(-EINVAL); + + for (i = 0; i < mbox->channel_count; i++) { + mchan = &mbox->channels[i]; + if (mchan->channel_id == pa->args[0] && + mchan->attrs.msg_proto_id == pa->args[1]) + return &mbox->controller.chans[i]; + } + + return ERR_PTR(-ENOENT); +} + +static int mpxy_mbox_populate_channels(struct mpxy_mbox *mbox) +{ + u32 i, *channel_ids __free(kfree) = NULL; + struct mpxy_mbox_channel *mchan; + int rc; + + /* Find-out of number of channels */ + rc = mpxy_get_channel_count(&mbox->channel_count); + if (rc) + return dev_err_probe(mbox->dev, rc, "failed to get number of MPXY channels\n"); + if (!mbox->channel_count) + return dev_err_probe(mbox->dev, -ENODEV, "no MPXY channels available\n"); + + /* Allocate and fetch all channel IDs */ + channel_ids = kzalloc_objs(*channel_ids, mbox->channel_count); + if (!channel_ids) + return -ENOMEM; + rc = mpxy_get_channel_ids(mbox->channel_count, channel_ids); + if (rc) + return dev_err_probe(mbox->dev, rc, "failed to get MPXY channel IDs\n"); + + /* Populate all channels */ + mbox->channels = devm_kcalloc(mbox->dev, mbox->channel_count, + sizeof(*mbox->channels), GFP_KERNEL); + if (!mbox->channels) + return -ENOMEM; + for (i = 0; i < mbox->channel_count; i++) { + mchan = &mbox->channels[i]; + mchan->mbox = mbox; + mchan->channel_id = channel_ids[i]; + + rc = mpxy_read_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSG_PROT_ID, + sizeof(mchan->attrs) / sizeof(u32), + (u32 *)&mchan->attrs); + if (rc) { + return dev_err_probe(mbox->dev, rc, + "MPXY channel 0x%x read attrs failed\n", + mchan->channel_id); + } + + if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) { + rc = mpxy_mbox_read_rpmi_attrs(mchan); + if (rc) { + return dev_err_probe(mbox->dev, rc, + "MPXY channel 0x%x read RPMI attrs failed\n", + mchan->channel_id); + } + } + + mchan->notif = devm_kzalloc(mbox->dev, mpxy_shmem_size, GFP_KERNEL); + if (!mchan->notif) + return -ENOMEM; + + mchan->max_xfer_len = min(mpxy_shmem_size, mchan->attrs.msg_max_len); + + if ((mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS) && + (mchan->attrs.capability & SBI_MPXY_CHAN_CAP_EVENTS_STATE)) + mchan->have_events_state = true; + + if ((mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS) && + (mchan->attrs.capability & SBI_MPXY_CHAN_CAP_MSI)) + mchan->msi_index = mbox->msi_count++; + else + mchan->msi_index = U32_MAX; + mchan->msi_irq = U32_MAX; + } + + return 0; +} + +static int mpxy_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mpxy_mbox_channel *mchan; + struct mpxy_mbox *mbox; + int msi_idx, rc; + u32 i; + + /* + * Initialize MPXY shared memory only once. This also ensures + * that SBI MPXY mailbox is probed only once. + */ + if (mpxy_shmem_init_done) { + dev_err(dev, "SBI MPXY mailbox already initialized\n"); + return -EALREADY; + } + + /* Probe for SBI MPXY extension */ + if (sbi_spec_version < sbi_mk_version(1, 0) || + sbi_probe_extension(SBI_EXT_MPXY) <= 0) { + dev_info(dev, "SBI MPXY extension not available\n"); + return -ENODEV; + } + + /* Find-out shared memory size */ + rc = mpxy_get_shmem_size(&mpxy_shmem_size); + if (rc) + return dev_err_probe(dev, rc, "failed to get MPXY shared memory size\n"); + + /* + * Setup MPXY shared memory on each CPU + * + * Note: Don't cleanup MPXY shared memory upon CPU power-down + * because the RPMI System MSI irqchip driver needs it to be + * available when migrating IRQs in CPU power-down path. + */ + cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/sbi-mpxy-shmem", + mpxy_setup_shmem, NULL); + + /* Mark as MPXY shared memory initialization done */ + mpxy_shmem_init_done = true; + + /* Allocate mailbox instance */ + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + mbox->dev = dev; + platform_set_drvdata(pdev, mbox); + + /* Populate mailbox channels */ + rc = mpxy_mbox_populate_channels(mbox); + if (rc) + return rc; + + /* Initialize mailbox controller */ + mbox->controller.txdone_irq = false; + mbox->controller.txdone_poll = false; + mbox->controller.ops = &mpxy_mbox_ops; + mbox->controller.dev = dev; + mbox->controller.num_chans = mbox->channel_count; + mbox->controller.fw_xlate = mpxy_mbox_fw_xlate; + mbox->controller.chans = devm_kcalloc(dev, mbox->channel_count, + sizeof(*mbox->controller.chans), + GFP_KERNEL); + if (!mbox->controller.chans) + return -ENOMEM; + for (i = 0; i < mbox->channel_count; i++) + mbox->controller.chans[i].con_priv = &mbox->channels[i]; + + /* Setup MSIs for mailbox (if required) */ + if (mbox->msi_count) { + /* + * The device MSI domain for platform devices on RISC-V architecture + * is only available after the MSI controller driver is probed so, + * explicitly configure here. + */ + if (!dev_get_msi_domain(dev)) { + struct fwnode_handle *fwnode = dev_fwnode(dev); + + /* + * The device MSI domain for OF devices is only set at the + * time of populating/creating OF device. If the device MSI + * domain is discovered later after the OF device is created + * then we need to set it explicitly before using any platform + * MSI functions. + */ + if (is_of_node(fwnode)) { + of_msi_configure(dev, dev_of_node(dev)); + } else if (is_acpi_device_node(fwnode)) { + struct irq_domain *msi_domain; + + msi_domain = irq_find_matching_fwnode(imsic_acpi_get_fwnode(dev), + DOMAIN_BUS_PLATFORM_MSI); + dev_set_msi_domain(dev, msi_domain); + } + + if (!dev_get_msi_domain(dev)) + return -EPROBE_DEFER; + } + + mbox->msi_index_to_channel = devm_kcalloc(dev, mbox->msi_count, + sizeof(*mbox->msi_index_to_channel), + GFP_KERNEL); + if (!mbox->msi_index_to_channel) + return -ENOMEM; + + for (msi_idx = 0; msi_idx < mbox->msi_count; msi_idx++) { + for (i = 0; i < mbox->channel_count; i++) { + mchan = &mbox->channels[i]; + if (mchan->msi_index == msi_idx) { + mbox->msi_index_to_channel[msi_idx] = mchan; + break; + } + } + } + + rc = platform_device_msi_init_and_alloc_irqs(dev, mbox->msi_count, + mpxy_mbox_msi_write); + if (rc) { + return dev_err_probe(dev, rc, "Failed to allocate %d MSIs\n", + mbox->msi_count); + } + + for (i = 0; i < mbox->channel_count; i++) { + mchan = &mbox->channels[i]; + if (mchan->msi_index == U32_MAX) + continue; + mchan->msi_irq = msi_get_virq(dev, mchan->msi_index); + } + } + + /* Register mailbox controller */ + rc = devm_mbox_controller_register(dev, &mbox->controller); + if (rc) { + dev_err_probe(dev, rc, "Registering SBI MPXY mailbox failed\n"); + if (mbox->msi_count) + platform_device_msi_free_irqs_all(dev); + return rc; + } + +#ifdef CONFIG_ACPI + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (adev) + acpi_dev_clear_dependencies(adev); +#endif + + dev_info(dev, "mailbox registered with %d channels\n", + mbox->channel_count); + return 0; +} + +static void mpxy_mbox_remove(struct platform_device *pdev) +{ + struct mpxy_mbox *mbox = platform_get_drvdata(pdev); + + if (mbox->msi_count) + platform_device_msi_free_irqs_all(mbox->dev); +} + +static const struct of_device_id mpxy_mbox_of_match[] = { + { .compatible = "riscv,sbi-mpxy-mbox" }, + {} +}; +MODULE_DEVICE_TABLE(of, mpxy_mbox_of_match); + +static const struct acpi_device_id mpxy_mbox_acpi_match[] = { + { "RSCV0005" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, mpxy_mbox_acpi_match); + +static struct platform_driver mpxy_mbox_driver = { + .driver = { + .name = "riscv-sbi-mpxy-mbox", + .of_match_table = mpxy_mbox_of_match, + .acpi_match_table = mpxy_mbox_acpi_match, + }, + .probe = mpxy_mbox_probe, + .remove = mpxy_mbox_remove, +}; +module_platform_driver(mpxy_mbox_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anup Patel <apatel@ventanamicro.com>"); +MODULE_DESCRIPTION("RISC-V SBI MPXY mailbox controller driver"); diff --git a/drivers/mailbox/rockchip-mailbox.c b/drivers/mailbox/rockchip-mailbox.c new file mode 100644 index 000000000..bc2f9dd79 --- /dev/null +++ b/drivers/mailbox/rockchip-mailbox.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define MAILBOX_A2B_INTEN 0x00 +#define MAILBOX_A2B_STATUS 0x04 +#define MAILBOX_A2B_CMD(x) (0x08 + (x) * 8) +#define MAILBOX_A2B_DAT(x) (0x0c + (x) * 8) + +#define MAILBOX_B2A_INTEN 0x28 +#define MAILBOX_B2A_STATUS 0x2C +#define MAILBOX_B2A_CMD(x) (0x30 + (x) * 8) +#define MAILBOX_B2A_DAT(x) (0x34 + (x) * 8) + +struct rockchip_mbox_msg { + u32 cmd; + int rx_size; +}; + +struct rockchip_mbox_data { + int num_chans; +}; + +struct rockchip_mbox_chan { + int idx; + int irq; + struct rockchip_mbox_msg *msg; + struct rockchip_mbox *mb; +}; + +struct rockchip_mbox { + struct mbox_controller mbox; + void __iomem *mbox_base; + + /* The maximum size of buf for each channel */ + u32 buf_size; + + struct rockchip_mbox_chan chans[]; +}; + +static int rockchip_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); + struct rockchip_mbox_msg *msg = data; + struct rockchip_mbox_chan *chans = mb->chans; + + if (!msg) + return -EINVAL; + + if (msg->rx_size > mb->buf_size) { + dev_err(mb->mbox.dev, "Transmit size over buf size(%d)\n", + mb->buf_size); + return -EINVAL; + } + + dev_dbg(mb->mbox.dev, "Chan[%d]: A2B message, cmd 0x%08x\n", + chans->idx, msg->cmd); + + mb->chans[chans->idx].msg = msg; + + writel_relaxed(msg->cmd, mb->mbox_base + MAILBOX_A2B_CMD(chans->idx)); + writel_relaxed(msg->rx_size, mb->mbox_base + + MAILBOX_A2B_DAT(chans->idx)); + + return 0; +} + +static int rockchip_mbox_startup(struct mbox_chan *chan) +{ + struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); + + /* Enable all B2A interrupts */ + writel_relaxed((1 << mb->mbox.num_chans) - 1, + mb->mbox_base + MAILBOX_B2A_INTEN); + + return 0; +} + +static void rockchip_mbox_shutdown(struct mbox_chan *chan) +{ + struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev); + struct rockchip_mbox_chan *chans = mb->chans; + + /* Disable all B2A interrupts */ + writel_relaxed(0, mb->mbox_base + MAILBOX_B2A_INTEN); + + mb->chans[chans->idx].msg = NULL; +} + +static const struct mbox_chan_ops rockchip_mbox_chan_ops = { + .send_data = rockchip_mbox_send_data, + .startup = rockchip_mbox_startup, + .shutdown = rockchip_mbox_shutdown, +}; + +static irqreturn_t rockchip_mbox_irq(int irq, void *dev_id) +{ + int idx; + struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; + u32 status = readl_relaxed(mb->mbox_base + MAILBOX_B2A_STATUS); + + for (idx = 0; idx < mb->mbox.num_chans; idx++) { + if ((status & (1 << idx)) && (irq == mb->chans[idx].irq)) { + /* Clear mbox interrupt */ + writel_relaxed(1 << idx, + mb->mbox_base + MAILBOX_B2A_STATUS); + return IRQ_WAKE_THREAD; + } + } + + return IRQ_NONE; +} + +static irqreturn_t rockchip_mbox_isr(int irq, void *dev_id) +{ + int idx; + struct rockchip_mbox_msg *msg = NULL; + struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id; + + for (idx = 0; idx < mb->mbox.num_chans; idx++) { + if (irq != mb->chans[idx].irq) + continue; + + msg = mb->chans[idx].msg; + if (!msg) { + dev_err(mb->mbox.dev, + "Chan[%d]: B2A message is NULL\n", idx); + break; /* spurious */ + } + + mbox_chan_received_data(&mb->mbox.chans[idx], msg); + mb->chans[idx].msg = NULL; + + dev_dbg(mb->mbox.dev, "Chan[%d]: B2A message, cmd 0x%08x\n", + idx, msg->cmd); + + break; + } + + return IRQ_HANDLED; +} + +static const struct rockchip_mbox_data rk3368_drv_data = { + .num_chans = 4, +}; + +static const struct of_device_id rockchip_mbox_of_match[] = { + { .compatible = "rockchip,rk3368-mailbox", .data = &rk3368_drv_data}, + { }, +}; +MODULE_DEVICE_TABLE(of, rockchip_mbox_of_match); + +static int rockchip_mbox_probe(struct platform_device *pdev) +{ + struct rockchip_mbox *mb; + const struct rockchip_mbox_data *drv_data; + struct resource *res; + struct clk *pclk; + int ret, irq, i; + + if (!pdev->dev.of_node) + return -ENODEV; + + drv_data = (const struct rockchip_mbox_data *) device_get_match_data(&pdev->dev); + + mb = devm_kzalloc(&pdev->dev, struct_size(mb, chans, drv_data->num_chans), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox.chans = devm_kcalloc(&pdev->dev, drv_data->num_chans, + sizeof(*mb->mbox.chans), GFP_KERNEL); + if (!mb->mbox.chans) + return -ENOMEM; + + platform_set_drvdata(pdev, mb); + + mb->mbox.dev = &pdev->dev; + mb->mbox.num_chans = drv_data->num_chans; + mb->mbox.ops = &rockchip_mbox_chan_ops; + mb->mbox.txdone_irq = true; + + mb->mbox_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(mb->mbox_base)) + return PTR_ERR(mb->mbox_base); + + /* Each channel has two buffers for A2B and B2A */ + mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); + + pclk = devm_clk_get_enabled(&pdev->dev, "pclk_mailbox"); + if (IS_ERR(pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pclk), + "failed to get and enable pclk_mailbox clock\n"); + + for (i = 0; i < mb->mbox.num_chans; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, + rockchip_mbox_irq, + rockchip_mbox_isr, IRQF_ONESHOT, + dev_name(&pdev->dev), mb); + if (ret < 0) + return ret; + + mb->chans[i].idx = i; + mb->chans[i].irq = irq; + mb->chans[i].mb = mb; + mb->chans[i].msg = NULL; + } + + ret = devm_mbox_controller_register(&pdev->dev, &mb->mbox); + if (ret < 0) + dev_err(&pdev->dev, "Failed to register mailbox: %d\n", ret); + + return ret; +} + +static struct platform_driver rockchip_mbox_driver = { + .probe = rockchip_mbox_probe, + .driver = { + .name = "rockchip-mailbox", + .of_match_table = rockchip_mbox_of_match, + }, +}; + +module_platform_driver(rockchip_mbox_driver); + +MODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU"); +MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>"); +MODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>"); diff --git a/drivers/mailbox/sprd-mailbox.c b/drivers/mailbox/sprd-mailbox.c new file mode 100644 index 000000000..9013d2ece --- /dev/null +++ b/drivers/mailbox/sprd-mailbox.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Spreadtrum mailbox driver + * + * Copyright (c) 2020 Spreadtrum Communications Inc. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#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 /* outbox only */ +#define SPRD_MBOX_IN_FIFO_STS2 0x24 /* inbox only, revision 2 */ + +/* 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 R2 inbox's SPRD_MBOX_FIFO_RST register */ +#define SPRD_INBOX_R2_FIFO_OVERFLOW_DELIVER_RST GENMASK(31, 0) + +/* Bit and mask definition for R2 inbox's SPRD_MBOX_FIFO_STS register */ +#define SPRD_INBOX_R2_FIFO_DELIVER_MASK GENMASK(15, 0) + +/* Bit and mask definition for SPRD_MBOX_IN_FIFO_STS2 register */ +#define SPRD_INBOX_R2_FIFO_OVERFLOW_MASK GENMASK(31, 16) +#define SPRD_INBOX_R2_FIFO_BUSY_MASK GENMASK(15, 0) + +/* Bit and mask definition for SPRD_MBOX_IRQ_STS register */ +#define SPRD_MBOX_IRQ_CLR GENMASK(31, 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_R1_CHAN_MAX 8 +#define SPRD_MBOX_R2_CHAN_MAX 16 + +enum sprd_mbox_version { + SPRD_MBOX_R1, + SPRD_MBOX_R2, +}; + +struct sprd_mbox_info { + enum sprd_mbox_version version; + unsigned long supp_id; +}; + +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; + u32 outbox_fifo_depth; + + const struct sprd_mbox_info *info; + + struct mutex lock; + u32 refcnt; + struct mbox_chan chan[SPRD_MBOX_R2_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, fifo_sts2, send_sts, busy, id; + + fifo_sts = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS); + + if (priv->info->version == SPRD_MBOX_R2) + fifo_sts2 = readl(priv->inbox_base + SPRD_MBOX_IN_FIFO_STS2); + + /* Get the inbox data delivery status */ + if (priv->info->version == SPRD_MBOX_R2) { + send_sts = fifo_sts & SPRD_INBOX_R2_FIFO_DELIVER_MASK; + } else { + 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; + } + + /* Clear FIFO delivery and overflow status first */ + if (priv->info->version == SPRD_MBOX_R2) { + writel(SPRD_INBOX_R2_FIFO_OVERFLOW_DELIVER_RST, + priv->inbox_base + SPRD_MBOX_FIFO_RST); + } else { + writel(fifo_sts & (SPRD_INBOX_FIFO_DELIVER_MASK | SPRD_INBOX_FIFO_OVERLOW_MASK), + priv->inbox_base + SPRD_MBOX_FIFO_RST); + } + + 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. + */ + if (priv->info->version == SPRD_MBOX_R2) + busy = fifo_sts2 & SPRD_INBOX_R2_FIFO_BUSY_MASK; + else + busy = fifo_sts & SPRD_INBOX_FIFO_BUSY_MASK; + + if (!(busy & BIT(id))) + mbox_chan_txdone(chan, 0); + } + + /* 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 delivery interrupt */ + val = SPRD_INBOX_FIFO_IRQ_MASK; + val &= ~SPRD_INBOX_FIFO_DELIVER_IRQ; + writel(val, priv->inbox_base + SPRD_MBOX_IRQ_MSK); + + /* Enable outbox FIFO not empty interrupt */ + val = SPRD_OUTBOX_FIFO_IRQ_MASK; + 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); + 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 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; + struct clk *clk; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + mutex_init(&priv->lock); + + priv->info = of_device_get_match_data(dev); + if (!priv->info) + return -EINVAL; + + /* + * 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); + + clk = devm_clk_get_enabled(dev, "enable"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get mailbox clock\n"); + return PTR_ERR(clk); + } + + 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) + 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) + 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) + return ret; + + if (!priv->info->supp_id) { + dev_err(dev, "no supplementary outbox specified\n"); + return -ENODEV; + } + priv->supp_base = priv->outbox_base + + (SPRD_OUTBOX_BASE_SPAN * priv->info->supp_id); + } + + /* 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.ops = &sprd_mbox_ops; + priv->mbox.txdone_irq = true; + + if (priv->info->version == SPRD_MBOX_R2) + priv->mbox.num_chans = SPRD_MBOX_R2_CHAN_MAX; + else + priv->mbox.num_chans = SPRD_MBOX_R1_CHAN_MAX; + + for (id = 0; id < priv->mbox.num_chans; 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 sprd_mbox_info sc9860_mbox_info = { + .version = SPRD_MBOX_R1, +}; + +static const struct sprd_mbox_info sc9863a_mbox_info = { + .version = SPRD_MBOX_R1, + .supp_id = 7, +}; + +static const struct sprd_mbox_info ums9230_mbox_info = { + .version = SPRD_MBOX_R2, + .supp_id = 6, +}; + +static const struct of_device_id sprd_mbox_of_match[] = { + { .compatible = "sprd,sc9860-mailbox", .data = &sc9860_mbox_info }, + { .compatible = "sprd,sc9863a-mailbox", .data = &sc9863a_mbox_info }, + { .compatible = "sprd,ums9230-mailbox", .data = &ums9230_mbox_info }, + { }, +}; +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 <baolin.wang@unisoc.com>"); +MODULE_DESCRIPTION("Spreadtrum mailbox driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/stm32-ipcc.c b/drivers/mailbox/stm32-ipcc.c new file mode 100644 index 000000000..c6134de1a --- /dev/null +++ b/drivers/mailbox/stm32-ipcc.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved + * Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics. + * Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> + +#define IPCC_XCR 0x000 +#define XCR_RXOIE BIT(0) +#define XCR_TXOIE BIT(16) + +#define IPCC_XMR 0x004 +#define IPCC_XSCR 0x008 +#define IPCC_XTOYSR 0x00c + +#define IPCC_PROC_OFFST 0x010 + +#define IPCC_HWCFGR 0x3f0 +#define IPCFGR_CHAN_MASK GENMASK(7, 0) + +#define IPCC_VER 0x3f4 +#define VER_MINREV_MASK GENMASK(3, 0) +#define VER_MAJREV_MASK GENMASK(7, 4) + +#define RX_BIT_MASK GENMASK(15, 0) +#define RX_BIT_CHAN(chan) BIT(chan) +#define TX_BIT_SHIFT 16 +#define TX_BIT_MASK GENMASK(31, 16) +#define TX_BIT_CHAN(chan) BIT(TX_BIT_SHIFT + (chan)) + +#define STM32_MAX_PROCS 2 + +enum { + IPCC_IRQ_RX, + IPCC_IRQ_TX, + IPCC_IRQ_NUM, +}; + +struct stm32_ipcc { + struct mbox_controller controller; + void __iomem *reg_base; + void __iomem *reg_proc; + struct clk *clk; + spinlock_t lock; /* protect access to IPCC registers */ + int irqs[IPCC_IRQ_NUM]; + u32 proc_id; + u32 n_chans; + u32 xcr; + u32 xmr; +}; + +static inline void stm32_ipcc_set_bits(spinlock_t *lock, void __iomem *reg, + u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(lock, flags); + writel_relaxed(readl_relaxed(reg) | mask, reg); + spin_unlock_irqrestore(lock, flags); +} + +static inline void stm32_ipcc_clr_bits(spinlock_t *lock, void __iomem *reg, + u32 mask) +{ + unsigned long flags; + + spin_lock_irqsave(lock, flags); + writel_relaxed(readl_relaxed(reg) & ~mask, reg); + spin_unlock_irqrestore(lock, flags); +} + +static irqreturn_t stm32_ipcc_rx_irq(int irq, void *data) +{ + struct stm32_ipcc *ipcc = data; + struct device *dev = ipcc->controller.dev; + u32 status, mr, tosr, chan; + irqreturn_t ret = IRQ_NONE; + int proc_offset; + + /* read 'channel occupied' status from other proc */ + proc_offset = ipcc->proc_id ? -IPCC_PROC_OFFST : IPCC_PROC_OFFST; + tosr = readl_relaxed(ipcc->reg_proc + proc_offset + IPCC_XTOYSR); + mr = readl_relaxed(ipcc->reg_proc + IPCC_XMR); + + /* search for unmasked 'channel occupied' */ + status = tosr & FIELD_GET(RX_BIT_MASK, ~mr); + + for (chan = 0; chan < ipcc->n_chans; chan++) { + if (!(status & (1 << chan))) + continue; + + dev_dbg(dev, "%s: chan:%d rx\n", __func__, chan); + + mbox_chan_received_data(&ipcc->controller.chans[chan], NULL); + + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XSCR, + RX_BIT_CHAN(chan)); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t stm32_ipcc_tx_irq(int irq, void *data) +{ + struct stm32_ipcc *ipcc = data; + struct device *dev = ipcc->controller.dev; + u32 status, mr, tosr, chan; + irqreturn_t ret = IRQ_NONE; + + tosr = readl_relaxed(ipcc->reg_proc + IPCC_XTOYSR); + mr = readl_relaxed(ipcc->reg_proc + IPCC_XMR); + + /* search for unmasked 'channel free' */ + status = ~tosr & FIELD_GET(TX_BIT_MASK, ~mr); + + for (chan = 0; chan < ipcc->n_chans ; chan++) { + if (!(status & (1 << chan))) + continue; + + dev_dbg(dev, "%s: chan:%d tx\n", __func__, chan); + + /* mask 'tx channel free' interrupt */ + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XMR, + TX_BIT_CHAN(chan)); + + mbox_chan_txdone(&ipcc->controller.chans[chan], 0); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static int stm32_ipcc_send_data(struct mbox_chan *link, void *data) +{ + unsigned long chan = (unsigned long)link->con_priv; + struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc, + controller); + + dev_dbg(ipcc->controller.dev, "%s: chan:%lu\n", __func__, chan); + + /* set channel n occupied */ + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XSCR, + TX_BIT_CHAN(chan)); + + /* unmask 'tx channel free' interrupt */ + stm32_ipcc_clr_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XMR, + TX_BIT_CHAN(chan)); + + return 0; +} + +static int stm32_ipcc_startup(struct mbox_chan *link) +{ + unsigned long chan = (unsigned long)link->con_priv; + struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc, + controller); + int ret; + + ret = clk_prepare_enable(ipcc->clk); + if (ret) { + dev_err(ipcc->controller.dev, "can not enable the clock\n"); + return ret; + } + + /* unmask 'rx channel occupied' interrupt */ + stm32_ipcc_clr_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XMR, + RX_BIT_CHAN(chan)); + + return 0; +} + +static void stm32_ipcc_shutdown(struct mbox_chan *link) +{ + unsigned long chan = (unsigned long)link->con_priv; + struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc, + controller); + + /* mask rx/tx interrupt */ + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XMR, + RX_BIT_CHAN(chan) | TX_BIT_CHAN(chan)); + + clk_disable_unprepare(ipcc->clk); +} + +static const struct mbox_chan_ops stm32_ipcc_ops = { + .send_data = stm32_ipcc_send_data, + .startup = stm32_ipcc_startup, + .shutdown = stm32_ipcc_shutdown, +}; + +static int stm32_ipcc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct stm32_ipcc *ipcc; + unsigned long i; + int ret; + u32 ip_ver; + static const char * const irq_name[] = {"rx", "tx"}; + irq_handler_t irq_thread[] = {stm32_ipcc_rx_irq, stm32_ipcc_tx_irq}; + + if (!np) { + dev_err(dev, "No DT found\n"); + return -ENODEV; + } + + ipcc = devm_kzalloc(dev, sizeof(*ipcc), GFP_KERNEL); + if (!ipcc) + return -ENOMEM; + + spin_lock_init(&ipcc->lock); + + /* proc_id */ + if (of_property_read_u32(np, "st,proc-id", &ipcc->proc_id)) { + dev_err(dev, "Missing st,proc-id\n"); + return -ENODEV; + } + + if (ipcc->proc_id >= STM32_MAX_PROCS) { + dev_err(dev, "Invalid proc_id (%d)\n", ipcc->proc_id); + return -EINVAL; + } + + /* regs */ + ipcc->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ipcc->reg_base)) + return PTR_ERR(ipcc->reg_base); + + ipcc->reg_proc = ipcc->reg_base + ipcc->proc_id * IPCC_PROC_OFFST; + + /* clock */ + ipcc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ipcc->clk)) + return PTR_ERR(ipcc->clk); + + ret = clk_prepare_enable(ipcc->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + + /* irq */ + for (i = 0; i < IPCC_IRQ_NUM; i++) { + ipcc->irqs[i] = platform_get_irq_byname(pdev, irq_name[i]); + if (ipcc->irqs[i] < 0) { + ret = ipcc->irqs[i]; + goto err_clk; + } + + ret = devm_request_threaded_irq(dev, ipcc->irqs[i], NULL, + irq_thread[i], IRQF_ONESHOT, + dev_name(dev), ipcc); + if (ret) + goto err_clk; + } + + /* mask and enable rx/tx irq */ + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XMR, + RX_BIT_MASK | TX_BIT_MASK); + stm32_ipcc_set_bits(&ipcc->lock, ipcc->reg_proc + IPCC_XCR, + XCR_RXOIE | XCR_TXOIE); + + /* wakeup */ + if (of_property_read_bool(np, "wakeup-source")) { + device_set_wakeup_capable(dev, true); + + ret = dev_pm_set_wake_irq(dev, ipcc->irqs[IPCC_IRQ_RX]); + if (ret) { + dev_err(dev, "Failed to set wake up irq\n"); + goto err_init_wkp; + } + } + + /* mailbox controller */ + ipcc->n_chans = readl_relaxed(ipcc->reg_base + IPCC_HWCFGR); + ipcc->n_chans &= IPCFGR_CHAN_MASK; + + ipcc->controller.dev = dev; + ipcc->controller.txdone_irq = true; + ipcc->controller.ops = &stm32_ipcc_ops; + ipcc->controller.num_chans = ipcc->n_chans; + ipcc->controller.chans = devm_kcalloc(dev, ipcc->controller.num_chans, + sizeof(*ipcc->controller.chans), + GFP_KERNEL); + if (!ipcc->controller.chans) { + ret = -ENOMEM; + goto err_irq_wkp; + } + + for (i = 0; i < ipcc->controller.num_chans; i++) + ipcc->controller.chans[i].con_priv = (void *)i; + + ret = devm_mbox_controller_register(dev, &ipcc->controller); + if (ret) + goto err_irq_wkp; + + platform_set_drvdata(pdev, ipcc); + + ip_ver = readl_relaxed(ipcc->reg_base + IPCC_VER); + + dev_info(dev, "ipcc rev:%ld.%ld enabled, %d chans, proc %d\n", + FIELD_GET(VER_MAJREV_MASK, ip_ver), + FIELD_GET(VER_MINREV_MASK, ip_ver), + ipcc->controller.num_chans, ipcc->proc_id); + + clk_disable_unprepare(ipcc->clk); + return 0; + +err_irq_wkp: + if (of_property_read_bool(np, "wakeup-source")) + dev_pm_clear_wake_irq(dev); +err_init_wkp: + device_set_wakeup_capable(dev, false); +err_clk: + clk_disable_unprepare(ipcc->clk); + return ret; +} + +static void stm32_ipcc_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (of_property_read_bool(dev->of_node, "wakeup-source")) + dev_pm_clear_wake_irq(&pdev->dev); + + device_set_wakeup_capable(dev, false); +} + +#ifdef CONFIG_PM_SLEEP +static int stm32_ipcc_suspend(struct device *dev) +{ + struct stm32_ipcc *ipcc = dev_get_drvdata(dev); + + ipcc->xmr = readl_relaxed(ipcc->reg_proc + IPCC_XMR); + ipcc->xcr = readl_relaxed(ipcc->reg_proc + IPCC_XCR); + + return 0; +} + +static int stm32_ipcc_resume(struct device *dev) +{ + struct stm32_ipcc *ipcc = dev_get_drvdata(dev); + + writel_relaxed(ipcc->xmr, ipcc->reg_proc + IPCC_XMR); + writel_relaxed(ipcc->xcr, ipcc->reg_proc + IPCC_XCR); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(stm32_ipcc_pm_ops, + stm32_ipcc_suspend, stm32_ipcc_resume); + +static const struct of_device_id stm32_ipcc_of_match[] = { + { .compatible = "st,stm32mp1-ipcc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_ipcc_of_match); + +static struct platform_driver stm32_ipcc_driver = { + .driver = { + .name = "stm32-ipcc", + .pm = &stm32_ipcc_pm_ops, + .of_match_table = stm32_ipcc_of_match, + }, + .probe = stm32_ipcc_probe, + .remove = stm32_ipcc_remove, +}; + +module_platform_driver(stm32_ipcc_driver); + +MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>"); +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>"); +MODULE_DESCRIPTION("STM32 IPCC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/sun6i-msgbox.c b/drivers/mailbox/sun6i-msgbox.c new file mode 100644 index 000000000..1cbc4d478 --- /dev/null +++ b/drivers/mailbox/sun6i-msgbox.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2017-2019 Samuel Holland <samuel@sholland.org> + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#define NUM_CHANS 8 + +#define CTRL_REG(n) (0x0000 + 0x4 * ((n) / 4)) +#define CTRL_RX(n) BIT(0 + 8 * ((n) % 4)) +#define CTRL_TX(n) BIT(4 + 8 * ((n) % 4)) + +#define REMOTE_IRQ_EN_REG 0x0040 +#define REMOTE_IRQ_STAT_REG 0x0050 +#define LOCAL_IRQ_EN_REG 0x0060 +#define LOCAL_IRQ_STAT_REG 0x0070 + +#define RX_IRQ(n) BIT(0 + 2 * (n)) +#define RX_IRQ_MASK 0x5555 +#define TX_IRQ(n) BIT(1 + 2 * (n)) +#define TX_IRQ_MASK 0xaaaa + +#define FIFO_STAT_REG(n) (0x0100 + 0x4 * (n)) +#define FIFO_STAT_MASK GENMASK(0, 0) + +#define MSG_STAT_REG(n) (0x0140 + 0x4 * (n)) +#define MSG_STAT_MASK GENMASK(2, 0) + +#define MSG_DATA_REG(n) (0x0180 + 0x4 * (n)) + +#define mbox_dbg(mbox, ...) dev_dbg((mbox)->controller.dev, __VA_ARGS__) + +struct sun6i_msgbox { + struct mbox_controller controller; + struct clk *clk; + spinlock_t lock; + void __iomem *regs; +}; + +static bool sun6i_msgbox_last_tx_done(struct mbox_chan *chan); +static bool sun6i_msgbox_peek_data(struct mbox_chan *chan); + +static inline int channel_number(struct mbox_chan *chan) +{ + return chan - chan->mbox->chans; +} + +static inline struct sun6i_msgbox *to_sun6i_msgbox(struct mbox_chan *chan) +{ + return chan->con_priv; +} + +static irqreturn_t sun6i_msgbox_irq(int irq, void *dev_id) +{ + struct sun6i_msgbox *mbox = dev_id; + uint32_t status; + int n; + + /* Only examine channels that are currently enabled. */ + status = readl(mbox->regs + LOCAL_IRQ_EN_REG) & + readl(mbox->regs + LOCAL_IRQ_STAT_REG); + + if (!(status & RX_IRQ_MASK)) + return IRQ_NONE; + + for (n = 0; n < NUM_CHANS; ++n) { + struct mbox_chan *chan = &mbox->controller.chans[n]; + + if (!(status & RX_IRQ(n))) + continue; + + while (sun6i_msgbox_peek_data(chan)) { + uint32_t msg = readl(mbox->regs + MSG_DATA_REG(n)); + + mbox_dbg(mbox, "Channel %d received 0x%08x\n", n, msg); + mbox_chan_received_data(chan, &msg); + } + + /* The IRQ can be cleared only once the FIFO is empty. */ + writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); + } + + return IRQ_HANDLED; +} + +static int sun6i_msgbox_send_data(struct mbox_chan *chan, void *data) +{ + struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); + int n = channel_number(chan); + uint32_t msg = *(uint32_t *)data; + + /* Using a channel backwards gets the hardware into a bad state. */ + if (WARN_ON_ONCE(!(readl(mbox->regs + CTRL_REG(n)) & CTRL_TX(n)))) + return 0; + + writel(msg, mbox->regs + MSG_DATA_REG(n)); + mbox_dbg(mbox, "Channel %d sent 0x%08x\n", n, msg); + + return 0; +} + +static int sun6i_msgbox_startup(struct mbox_chan *chan) +{ + struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); + int n = channel_number(chan); + + /* The coprocessor is responsible for setting channel directions. */ + if (readl(mbox->regs + CTRL_REG(n)) & CTRL_RX(n)) { + /* Flush the receive FIFO. */ + while (sun6i_msgbox_peek_data(chan)) + readl(mbox->regs + MSG_DATA_REG(n)); + writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); + + /* Enable the receive IRQ. */ + spin_lock(&mbox->lock); + writel(readl(mbox->regs + LOCAL_IRQ_EN_REG) | RX_IRQ(n), + mbox->regs + LOCAL_IRQ_EN_REG); + spin_unlock(&mbox->lock); + } + + mbox_dbg(mbox, "Channel %d startup complete\n", n); + + return 0; +} + +static void sun6i_msgbox_shutdown(struct mbox_chan *chan) +{ + struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); + int n = channel_number(chan); + + if (readl(mbox->regs + CTRL_REG(n)) & CTRL_RX(n)) { + /* Disable the receive IRQ. */ + spin_lock(&mbox->lock); + writel(readl(mbox->regs + LOCAL_IRQ_EN_REG) & ~RX_IRQ(n), + mbox->regs + LOCAL_IRQ_EN_REG); + spin_unlock(&mbox->lock); + + /* Attempt to flush the FIFO until the IRQ is cleared. */ + do { + while (sun6i_msgbox_peek_data(chan)) + readl(mbox->regs + MSG_DATA_REG(n)); + writel(RX_IRQ(n), mbox->regs + LOCAL_IRQ_STAT_REG); + } while (readl(mbox->regs + LOCAL_IRQ_STAT_REG) & RX_IRQ(n)); + } + + mbox_dbg(mbox, "Channel %d shutdown complete\n", n); +} + +static bool sun6i_msgbox_last_tx_done(struct mbox_chan *chan) +{ + struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); + int n = channel_number(chan); + + /* + * The hardware allows snooping on the remote user's IRQ statuses. + * We consider a message to be acknowledged only once the receive IRQ + * for that channel is cleared. Since the receive IRQ for a channel + * cannot be cleared until the FIFO for that channel is empty, this + * ensures that the message has actually been read. It also gives the + * recipient an opportunity to perform minimal processing before + * acknowledging the message. + */ + return !(readl(mbox->regs + REMOTE_IRQ_STAT_REG) & RX_IRQ(n)); +} + +static bool sun6i_msgbox_peek_data(struct mbox_chan *chan) +{ + struct sun6i_msgbox *mbox = to_sun6i_msgbox(chan); + int n = channel_number(chan); + + return readl(mbox->regs + MSG_STAT_REG(n)) & MSG_STAT_MASK; +} + +static const struct mbox_chan_ops sun6i_msgbox_chan_ops = { + .send_data = sun6i_msgbox_send_data, + .startup = sun6i_msgbox_startup, + .shutdown = sun6i_msgbox_shutdown, + .last_tx_done = sun6i_msgbox_last_tx_done, + .peek_data = sun6i_msgbox_peek_data, +}; + +static int sun6i_msgbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mbox_chan *chans; + struct reset_control *reset; + struct sun6i_msgbox *mbox; + int i, ret; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + chans = devm_kcalloc(dev, NUM_CHANS, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + + for (i = 0; i < NUM_CHANS; ++i) + chans[i].con_priv = mbox; + + mbox->clk = devm_clk_get(dev, NULL); + if (IS_ERR(mbox->clk)) { + ret = PTR_ERR(mbox->clk); + dev_err(dev, "Failed to get clock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(mbox->clk); + if (ret) { + dev_err(dev, "Failed to enable clock: %d\n", ret); + return ret; + } + + reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(reset)) { + ret = PTR_ERR(reset); + dev_err(dev, "Failed to get reset control: %d\n", ret); + goto err_disable_unprepare; + } + + /* + * NOTE: We rely on platform firmware to preconfigure the channel + * directions, and we share this hardware block with other firmware + * that runs concurrently with Linux (e.g. a trusted monitor). + * + * Therefore, we do *not* assert the reset line if probing fails or + * when removing the device. + */ + ret = reset_control_deassert(reset); + if (ret) { + dev_err(dev, "Failed to deassert reset: %d\n", ret); + goto err_disable_unprepare; + } + + mbox->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->regs)) { + ret = PTR_ERR(mbox->regs); + dev_err(dev, "Failed to map MMIO resource: %d\n", ret); + goto err_disable_unprepare; + } + + /* Disable all IRQs for this end of the msgbox. */ + writel(0, mbox->regs + LOCAL_IRQ_EN_REG); + + ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), + sun6i_msgbox_irq, 0, dev_name(dev), mbox); + if (ret) + goto err_disable_unprepare; + + mbox->controller.dev = dev; + mbox->controller.ops = &sun6i_msgbox_chan_ops; + mbox->controller.chans = chans; + mbox->controller.num_chans = NUM_CHANS; + mbox->controller.txdone_irq = false; + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + + spin_lock_init(&mbox->lock); + platform_set_drvdata(pdev, mbox); + + ret = mbox_controller_register(&mbox->controller); + if (ret) { + dev_err(dev, "Failed to register controller: %d\n", ret); + goto err_disable_unprepare; + } + + return 0; + +err_disable_unprepare: + clk_disable_unprepare(mbox->clk); + + return ret; +} + +static void sun6i_msgbox_remove(struct platform_device *pdev) +{ + struct sun6i_msgbox *mbox = platform_get_drvdata(pdev); + + mbox_controller_unregister(&mbox->controller); + /* See the comment in sun6i_msgbox_probe about the reset line. */ + clk_disable_unprepare(mbox->clk); +} + +static const struct of_device_id sun6i_msgbox_of_match[] = { + { .compatible = "allwinner,sun6i-a31-msgbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sun6i_msgbox_of_match); + +static struct platform_driver sun6i_msgbox_driver = { + .driver = { + .name = "sun6i-msgbox", + .of_match_table = sun6i_msgbox_of_match, + }, + .probe = sun6i_msgbox_probe, + .remove = sun6i_msgbox_remove, +}; +module_platform_driver(sun6i_msgbox_driver); + +MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>"); +MODULE_DESCRIPTION("Allwinner sun6i/sun8i/sun9i/sun50i Message Box"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c new file mode 100644 index 000000000..03ee73aad --- /dev/null +++ b/drivers/mailbox/tegra-hsp.c @@ -0,0 +1,1002 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2025, NVIDIA CORPORATION. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mailbox_controller.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> + +#include <soc/tegra/fuse.h> + +#include <dt-bindings/mailbox/tegra186-hsp.h> + +#define HSP_INT_IE(x) (0x100 + ((x) * 4)) +#define HSP_INT_IV 0x300 +#define HSP_INT_IR 0x304 + +#define HSP_INT_EMPTY_SHIFT 0 +#define HSP_INT_EMPTY_MASK 0xff +#define HSP_INT_FULL_SHIFT 8 +#define HSP_INT_FULL_MASK 0xff + +#define HSP_INT_DIMENSIONING 0x380 + +#define HSP_DB_TRIGGER 0x0 +#define HSP_DB_ENABLE 0x4 +#define HSP_DB_RAW 0x8 +#define HSP_DB_PENDING 0xc + +#define HSP_SM_SHRD_MBOX 0x0 +#define HSP_SM_SHRD_MBOX_FULL BIT(31) +#define HSP_SM_SHRD_MBOX_FULL_INT_IE 0x04 +#define HSP_SM_SHRD_MBOX_EMPTY_INT_IE 0x08 + +#define HSP_SHRD_MBOX_TYPE1_TAG 0x40 +#define HSP_SHRD_MBOX_TYPE1_DATA0 0x48 +#define HSP_SHRD_MBOX_TYPE1_DATA1 0x4c +#define HSP_SHRD_MBOX_TYPE1_DATA2 0x50 +#define HSP_SHRD_MBOX_TYPE1_DATA3 0x54 + +#define HSP_DB_CCPLEX 1 +#define HSP_DB_BPMP 3 +#define HSP_DB_MAX 7 + +#define HSP_MBOX_TYPE_MASK 0xff + +struct tegra_hsp_channel; +struct tegra_hsp; + +struct tegra_hsp_channel { + struct tegra_hsp *hsp; + struct mbox_chan *chan; + void __iomem *regs; +}; + +struct tegra_hsp_doorbell { + struct tegra_hsp_channel channel; + struct list_head list; + const char *name; + unsigned int master; + unsigned int index; +}; + +struct tegra_hsp_sm_ops { + void (*send)(struct tegra_hsp_channel *channel, void *data); + void (*recv)(struct tegra_hsp_channel *channel); +}; + +struct tegra_hsp_mailbox { + struct tegra_hsp_channel channel; + const struct tegra_hsp_sm_ops *ops; + unsigned int index; + bool producer; +}; + +struct tegra_hsp_db_map { + const char *name; + unsigned int master; + unsigned int index; +}; + +struct tegra_hsp_soc { + const struct tegra_hsp_db_map *map; + bool has_per_mb_ie; + bool has_128_bit_mb; + unsigned int reg_stride; + + /* Shifts for dimensioning register. */ + unsigned int si_shift; + unsigned int db_shift; + unsigned int as_shift; + unsigned int ss_shift; + unsigned int sm_shift; + + /* Masks for dimensioning register. */ + unsigned int si_mask; + unsigned int db_mask; + unsigned int as_mask; + unsigned int ss_mask; + unsigned int sm_mask; +}; + +struct tegra_hsp { + struct device *dev; + const struct tegra_hsp_soc *soc; + struct mbox_controller mbox_db; + struct mbox_controller mbox_sm; + void __iomem *regs; + unsigned int doorbell_irq; + unsigned int *shared_irqs; + unsigned int shared_irq; + unsigned int num_sm; + unsigned int num_as; + unsigned int num_ss; + unsigned int num_db; + unsigned int num_si; + + spinlock_t lock; + struct lock_class_key lock_key; + + struct list_head doorbells; + struct tegra_hsp_mailbox *mailboxes; + + unsigned long mask; +}; + +static inline u32 tegra_hsp_readl(struct tegra_hsp *hsp, unsigned int offset) +{ + return readl(hsp->regs + offset); +} + +static inline void tegra_hsp_writel(struct tegra_hsp *hsp, u32 value, + unsigned int offset) +{ + writel(value, hsp->regs + offset); +} + +static inline u32 tegra_hsp_channel_readl(struct tegra_hsp_channel *channel, + unsigned int offset) +{ + return readl(channel->regs + offset); +} + +static inline void tegra_hsp_channel_writel(struct tegra_hsp_channel *channel, + u32 value, unsigned int offset) +{ + writel(value, channel->regs + offset); +} + +static bool tegra_hsp_doorbell_can_ring(struct tegra_hsp_doorbell *db) +{ + u32 value; + + value = tegra_hsp_channel_readl(&db->channel, HSP_DB_ENABLE); + + return (value & BIT(TEGRA_HSP_DB_MASTER_CCPLEX)) != 0; +} + +static struct tegra_hsp_doorbell * +__tegra_hsp_doorbell_get(struct tegra_hsp *hsp, unsigned int master) +{ + struct tegra_hsp_doorbell *entry; + + list_for_each_entry(entry, &hsp->doorbells, list) + if (entry->master == master) + return entry; + + return NULL; +} + +static struct tegra_hsp_doorbell * +tegra_hsp_doorbell_get(struct tegra_hsp *hsp, unsigned int master) +{ + struct tegra_hsp_doorbell *db; + unsigned long flags; + + spin_lock_irqsave(&hsp->lock, flags); + db = __tegra_hsp_doorbell_get(hsp, master); + spin_unlock_irqrestore(&hsp->lock, flags); + + return db; +} + +static irqreturn_t tegra_hsp_doorbell_irq(int irq, void *data) +{ + struct tegra_hsp *hsp = data; + struct tegra_hsp_doorbell *db; + unsigned long master, value; + + db = tegra_hsp_doorbell_get(hsp, TEGRA_HSP_DB_MASTER_CCPLEX); + if (!db) + return IRQ_NONE; + + value = tegra_hsp_channel_readl(&db->channel, HSP_DB_PENDING); + tegra_hsp_channel_writel(&db->channel, value, HSP_DB_PENDING); + + spin_lock(&hsp->lock); + + for_each_set_bit(master, &value, hsp->mbox_db.num_chans) { + struct tegra_hsp_doorbell *db; + + db = __tegra_hsp_doorbell_get(hsp, master); + /* + * Depending on the bootloader chain, the CCPLEX doorbell will + * have some doorbells enabled, which means that requesting an + * interrupt will immediately fire. + * + * In that case, db->channel.chan will still be NULL here and + * cause a crash if not properly guarded. + * + * It remains to be seen if ignoring the doorbell in that case + * is the correct solution. + */ + if (db && db->channel.chan) + mbox_chan_received_data(db->channel.chan, NULL); + } + + spin_unlock(&hsp->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t tegra_hsp_shared_irq(int irq, void *data) +{ + struct tegra_hsp *hsp = data; + unsigned long bit, mask; + u32 status; + + status = tegra_hsp_readl(hsp, HSP_INT_IR) & hsp->mask; + + /* process EMPTY interrupts first */ + mask = (status >> HSP_INT_EMPTY_SHIFT) & HSP_INT_EMPTY_MASK; + + for_each_set_bit(bit, &mask, hsp->num_sm) { + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[bit]; + + if (mb->producer) { + /* + * Disable EMPTY interrupts until data is sent with + * the next message. These interrupts are level- + * triggered, so if we kept them enabled they would + * constantly trigger until we next write data into + * the message. + */ + spin_lock(&hsp->lock); + + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); + tegra_hsp_writel(hsp, hsp->mask, + HSP_INT_IE(hsp->shared_irq)); + + spin_unlock(&hsp->lock); + + mbox_chan_txdone(mb->channel.chan, 0); + } + } + + /* process FULL interrupts */ + mask = (status >> HSP_INT_FULL_SHIFT) & HSP_INT_FULL_MASK; + + for_each_set_bit(bit, &mask, hsp->num_sm) { + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[bit]; + + if (!mb->producer) + mb->ops->recv(&mb->channel); + } + + return IRQ_HANDLED; +} + +static struct tegra_hsp_channel * +tegra_hsp_doorbell_create(struct tegra_hsp *hsp, const char *name, + unsigned int master, unsigned int index) +{ + struct tegra_hsp_doorbell *db; + unsigned int offset; + unsigned long flags; + + db = devm_kzalloc(hsp->dev, sizeof(*db), GFP_KERNEL); + if (!db) + return ERR_PTR(-ENOMEM); + + offset = (1 + (hsp->num_sm / 2) + hsp->num_ss + hsp->num_as) * SZ_64K; + offset += index * hsp->soc->reg_stride; + + db->channel.regs = hsp->regs + offset; + db->channel.hsp = hsp; + + db->name = devm_kstrdup_const(hsp->dev, name, GFP_KERNEL); + db->master = master; + db->index = index; + + spin_lock_irqsave(&hsp->lock, flags); + list_add_tail(&db->list, &hsp->doorbells); + spin_unlock_irqrestore(&hsp->lock, flags); + + return &db->channel; +} + +static int tegra_hsp_doorbell_send_data(struct mbox_chan *chan, void *data) +{ + struct tegra_hsp_doorbell *db = chan->con_priv; + + tegra_hsp_channel_writel(&db->channel, 1, HSP_DB_TRIGGER); + + return 0; +} + +static int tegra_hsp_doorbell_startup(struct mbox_chan *chan) +{ + struct tegra_hsp_doorbell *db = chan->con_priv; + struct tegra_hsp *hsp = db->channel.hsp; + struct tegra_hsp_doorbell *ccplex; + unsigned long flags; + u32 value; + + if (db->master >= chan->mbox->num_chans) { + dev_err(chan->mbox->dev, + "invalid master ID %u for HSP channel\n", + db->master); + return -EINVAL; + } + + ccplex = tegra_hsp_doorbell_get(hsp, TEGRA_HSP_DB_MASTER_CCPLEX); + if (!ccplex) + return -ENODEV; + + /* + * On simulation platforms the BPMP hasn't had a chance yet to mark + * the doorbell as ringable by the CCPLEX, so we want to skip extra + * checks here. + */ + if (tegra_is_silicon() && !tegra_hsp_doorbell_can_ring(db)) + return -ENODEV; + + spin_lock_irqsave(&hsp->lock, flags); + + value = tegra_hsp_channel_readl(&ccplex->channel, HSP_DB_ENABLE); + value |= BIT(db->master); + tegra_hsp_channel_writel(&ccplex->channel, value, HSP_DB_ENABLE); + + spin_unlock_irqrestore(&hsp->lock, flags); + + return 0; +} + +static void tegra_hsp_doorbell_shutdown(struct mbox_chan *chan) +{ + struct tegra_hsp_doorbell *db = chan->con_priv; + struct tegra_hsp *hsp = db->channel.hsp; + struct tegra_hsp_doorbell *ccplex; + unsigned long flags; + u32 value; + + ccplex = tegra_hsp_doorbell_get(hsp, TEGRA_HSP_DB_MASTER_CCPLEX); + if (!ccplex) + return; + + spin_lock_irqsave(&hsp->lock, flags); + + value = tegra_hsp_channel_readl(&ccplex->channel, HSP_DB_ENABLE); + value &= ~BIT(db->master); + tegra_hsp_channel_writel(&ccplex->channel, value, HSP_DB_ENABLE); + + spin_unlock_irqrestore(&hsp->lock, flags); +} + +static const struct mbox_chan_ops tegra_hsp_db_ops = { + .send_data = tegra_hsp_doorbell_send_data, + .startup = tegra_hsp_doorbell_startup, + .shutdown = tegra_hsp_doorbell_shutdown, +}; + +static void tegra_hsp_sm_send32(struct tegra_hsp_channel *channel, void *data) +{ + u32 value; + + /* copy data and mark mailbox full */ + value = (u32)(unsigned long)data; + value |= HSP_SM_SHRD_MBOX_FULL; + + tegra_hsp_channel_writel(channel, value, HSP_SM_SHRD_MBOX); +} + +static void tegra_hsp_sm_recv32(struct tegra_hsp_channel *channel) +{ + u32 value; + void *msg; + + value = tegra_hsp_channel_readl(channel, HSP_SM_SHRD_MBOX); + value &= ~HSP_SM_SHRD_MBOX_FULL; + msg = (void *)(unsigned long)value; + + /* + * Need to clear all bits here since some producers, such as TCU, depend + * on fields in the register getting cleared by the consumer. + * + * The mailbox API doesn't give the consumers a way of doing that + * explicitly, so we have to make sure we cover all possible cases. + */ + tegra_hsp_channel_writel(channel, 0x0, HSP_SM_SHRD_MBOX); + + mbox_chan_received_data(channel->chan, msg); +} + +static const struct tegra_hsp_sm_ops tegra_hsp_sm_32bit_ops = { + .send = tegra_hsp_sm_send32, + .recv = tegra_hsp_sm_recv32, +}; + +static void tegra_hsp_sm_send128(struct tegra_hsp_channel *channel, void *data) +{ + u32 value[4]; + + memcpy(value, data, sizeof(value)); + + /* Copy data */ + tegra_hsp_channel_writel(channel, value[0], HSP_SHRD_MBOX_TYPE1_DATA0); + tegra_hsp_channel_writel(channel, value[1], HSP_SHRD_MBOX_TYPE1_DATA1); + tegra_hsp_channel_writel(channel, value[2], HSP_SHRD_MBOX_TYPE1_DATA2); + tegra_hsp_channel_writel(channel, value[3], HSP_SHRD_MBOX_TYPE1_DATA3); + + /* Update tag to mark mailbox full */ + tegra_hsp_channel_writel(channel, HSP_SM_SHRD_MBOX_FULL, + HSP_SHRD_MBOX_TYPE1_TAG); +} + +static void tegra_hsp_sm_recv128(struct tegra_hsp_channel *channel) +{ + u32 value[4]; + void *msg; + + value[0] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA0); + value[1] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA1); + value[2] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA2); + value[3] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA3); + + msg = (void *)(unsigned long)value; + + /* + * Clear data registers and tag. + */ + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA0); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA1); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA2); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA3); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_TAG); + + mbox_chan_received_data(channel->chan, msg); +} + +static const struct tegra_hsp_sm_ops tegra_hsp_sm_128bit_ops = { + .send = tegra_hsp_sm_send128, + .recv = tegra_hsp_sm_recv128, +}; + +static int tegra_hsp_mailbox_send_data(struct mbox_chan *chan, void *data) +{ + struct tegra_hsp_mailbox *mb = chan->con_priv; + struct tegra_hsp *hsp = mb->channel.hsp; + unsigned long flags; + + if (WARN_ON(!mb->producer)) + return -EPERM; + + mb->ops->send(&mb->channel, data); + + /* enable EMPTY interrupt for the shared mailbox */ + spin_lock_irqsave(&hsp->lock, flags); + + hsp->mask |= BIT(HSP_INT_EMPTY_SHIFT + mb->index); + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); + + spin_unlock_irqrestore(&hsp->lock, flags); + + return 0; +} + +static int tegra_hsp_mailbox_flush(struct mbox_chan *chan, + unsigned long timeout) +{ + struct tegra_hsp_mailbox *mb = chan->con_priv; + struct tegra_hsp_channel *ch = &mb->channel; + u32 value; + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + value = tegra_hsp_channel_readl(ch, HSP_SM_SHRD_MBOX); + if ((value & HSP_SM_SHRD_MBOX_FULL) == 0) { + mbox_chan_txdone(chan, 0); + + /* Wait until channel is empty */ + if (chan->active_req != MBOX_NO_MSG) + continue; + + return 0; + } + + udelay(1); + } + + return -ETIME; +} + +static int tegra_hsp_mailbox_startup(struct mbox_chan *chan) +{ + struct tegra_hsp_mailbox *mb = chan->con_priv; + struct tegra_hsp_channel *ch = &mb->channel; + struct tegra_hsp *hsp = mb->channel.hsp; + unsigned long flags; + + chan->txdone_method = MBOX_TXDONE_BY_IRQ; + + /* + * Shared mailboxes start out as consumers by default. FULL and EMPTY + * interrupts are coalesced at the same shared interrupt. + * + * Keep EMPTY interrupts disabled at startup and only enable them when + * the mailbox is actually full. This is required because the FULL and + * EMPTY interrupts are level-triggered, so keeping EMPTY interrupts + * enabled all the time would cause an interrupt storm while mailboxes + * are idle. + */ + + spin_lock_irqsave(&hsp->lock, flags); + + if (mb->producer) + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); + else + hsp->mask |= BIT(HSP_INT_FULL_SHIFT + mb->index); + + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); + + spin_unlock_irqrestore(&hsp->lock, flags); + + if (hsp->soc->has_per_mb_ie) { + if (mb->producer) + tegra_hsp_channel_writel(ch, 0x0, + HSP_SM_SHRD_MBOX_EMPTY_INT_IE); + else + tegra_hsp_channel_writel(ch, 0x1, + HSP_SM_SHRD_MBOX_FULL_INT_IE); + } + + return 0; +} + +static void tegra_hsp_mailbox_shutdown(struct mbox_chan *chan) +{ + struct tegra_hsp_mailbox *mb = chan->con_priv; + struct tegra_hsp_channel *ch = &mb->channel; + struct tegra_hsp *hsp = mb->channel.hsp; + unsigned long flags; + + if (hsp->soc->has_per_mb_ie) { + if (mb->producer) + tegra_hsp_channel_writel(ch, 0x0, + HSP_SM_SHRD_MBOX_EMPTY_INT_IE); + else + tegra_hsp_channel_writel(ch, 0x0, + HSP_SM_SHRD_MBOX_FULL_INT_IE); + } + + spin_lock_irqsave(&hsp->lock, flags); + + if (mb->producer) + hsp->mask &= ~BIT(HSP_INT_EMPTY_SHIFT + mb->index); + else + hsp->mask &= ~BIT(HSP_INT_FULL_SHIFT + mb->index); + + tegra_hsp_writel(hsp, hsp->mask, HSP_INT_IE(hsp->shared_irq)); + + spin_unlock_irqrestore(&hsp->lock, flags); +} + +static const struct mbox_chan_ops tegra_hsp_sm_ops = { + .send_data = tegra_hsp_mailbox_send_data, + .flush = tegra_hsp_mailbox_flush, + .startup = tegra_hsp_mailbox_startup, + .shutdown = tegra_hsp_mailbox_shutdown, +}; + +static struct mbox_chan *tegra_hsp_db_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *args) +{ + struct tegra_hsp *hsp = container_of(mbox, struct tegra_hsp, mbox_db); + unsigned int type = args->args[0], master = args->args[1]; + struct tegra_hsp_channel *channel = ERR_PTR(-ENODEV); + struct tegra_hsp_doorbell *db; + struct mbox_chan *chan; + unsigned long flags; + unsigned int i; + + if (type != TEGRA_HSP_MBOX_TYPE_DB || !hsp->doorbell_irq) + return ERR_PTR(-ENODEV); + + db = tegra_hsp_doorbell_get(hsp, master); + if (db) + channel = &db->channel; + + if (IS_ERR(channel)) + return ERR_CAST(channel); + + spin_lock_irqsave(&hsp->lock, flags); + + for (i = 0; i < mbox->num_chans; i++) { + chan = &mbox->chans[i]; + if (!chan->con_priv) { + channel->chan = chan; + chan->con_priv = db; + break; + } + + chan = NULL; + } + + spin_unlock_irqrestore(&hsp->lock, flags); + + return chan ?: ERR_PTR(-EBUSY); +} + +static struct mbox_chan *tegra_hsp_sm_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *args) +{ + struct tegra_hsp *hsp = container_of(mbox, struct tegra_hsp, mbox_sm); + unsigned int type = args->args[0], index; + struct tegra_hsp_mailbox *mb; + + index = args->args[1] & TEGRA_HSP_SM_MASK; + + if ((type & HSP_MBOX_TYPE_MASK) != TEGRA_HSP_MBOX_TYPE_SM || + !hsp->shared_irqs || index >= hsp->num_sm) + return ERR_PTR(-ENODEV); + + mb = &hsp->mailboxes[index]; + + if (type & TEGRA_HSP_MBOX_TYPE_SM_128BIT) { + if (!hsp->soc->has_128_bit_mb) + return ERR_PTR(-ENODEV); + + mb->ops = &tegra_hsp_sm_128bit_ops; + } else { + mb->ops = &tegra_hsp_sm_32bit_ops; + } + + if ((args->args[1] & TEGRA_HSP_SM_FLAG_TX) == 0) + mb->producer = false; + else + mb->producer = true; + + return mb->channel.chan; +} + +static int tegra_hsp_add_doorbells(struct tegra_hsp *hsp) +{ + const struct tegra_hsp_db_map *map = hsp->soc->map; + struct tegra_hsp_channel *channel; + + while (map->name) { + channel = tegra_hsp_doorbell_create(hsp, map->name, + map->master, map->index); + if (IS_ERR(channel)) + return PTR_ERR(channel); + + map++; + } + + return 0; +} + +static int tegra_hsp_add_mailboxes(struct tegra_hsp *hsp, struct device *dev) +{ + int i; + + hsp->mailboxes = devm_kcalloc(dev, hsp->num_sm, sizeof(*hsp->mailboxes), + GFP_KERNEL); + if (!hsp->mailboxes) + return -ENOMEM; + + for (i = 0; i < hsp->num_sm; i++) { + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[i]; + + mb->index = i; + + mb->channel.hsp = hsp; + mb->channel.regs = hsp->regs + SZ_64K + i * SZ_32K; + mb->channel.chan = &hsp->mbox_sm.chans[i]; + mb->channel.chan->con_priv = mb; + } + + return 0; +} + +static int tegra_hsp_request_shared_irq(struct tegra_hsp *hsp) +{ + unsigned int i, irq = 0; + int err; + + for (i = 0; i < hsp->num_si; i++) { + irq = hsp->shared_irqs[i]; + if (irq <= 0) + continue; + + err = devm_request_irq(hsp->dev, irq, tegra_hsp_shared_irq, 0, + dev_name(hsp->dev), hsp); + if (err < 0) + continue; + + hsp->shared_irq = i; + + /* disable all interrupts */ + tegra_hsp_writel(hsp, 0, HSP_INT_IE(hsp->shared_irq)); + + dev_dbg(hsp->dev, "interrupt requested: %u\n", irq); + + break; + } + + if (i == hsp->num_si) { + dev_err(hsp->dev, "failed to find available interrupt\n"); + return -ENOENT; + } + + return 0; +} + +static int tegra_hsp_probe(struct platform_device *pdev) +{ + struct tegra_hsp *hsp; + unsigned int i; + u32 value; + int err; + + hsp = devm_kzalloc(&pdev->dev, sizeof(*hsp), GFP_KERNEL); + if (!hsp) + return -ENOMEM; + + hsp->dev = &pdev->dev; + hsp->soc = of_device_get_match_data(&pdev->dev); + INIT_LIST_HEAD(&hsp->doorbells); + spin_lock_init(&hsp->lock); + + hsp->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hsp->regs)) + return PTR_ERR(hsp->regs); + + value = tegra_hsp_readl(hsp, HSP_INT_DIMENSIONING); + hsp->num_sm = (value >> hsp->soc->sm_shift) & hsp->soc->sm_mask; + hsp->num_ss = (value >> hsp->soc->ss_shift) & hsp->soc->ss_mask; + hsp->num_as = (value >> hsp->soc->as_shift) & hsp->soc->as_mask; + hsp->num_db = (value >> hsp->soc->db_shift) & hsp->soc->db_mask; + hsp->num_si = (value >> hsp->soc->si_shift) & hsp->soc->si_mask; + + err = platform_get_irq_byname_optional(pdev, "doorbell"); + if (err >= 0) + hsp->doorbell_irq = err; + + if (hsp->num_si > 0) { + unsigned int count = 0; + + hsp->shared_irqs = devm_kcalloc(&pdev->dev, hsp->num_si, + sizeof(*hsp->shared_irqs), + GFP_KERNEL); + if (!hsp->shared_irqs) + return -ENOMEM; + + for (i = 0; i < hsp->num_si; i++) { + char *name; + + name = kasprintf(GFP_KERNEL, "shared%u", i); + if (!name) + return -ENOMEM; + + err = platform_get_irq_byname_optional(pdev, name); + if (err >= 0) { + hsp->shared_irqs[i] = err; + count++; + } + + kfree(name); + } + + if (count == 0) { + devm_kfree(&pdev->dev, hsp->shared_irqs); + hsp->shared_irqs = NULL; + } + } + + /* setup the doorbell controller */ + hsp->mbox_db.of_xlate = tegra_hsp_db_xlate; + hsp->mbox_db.num_chans = 32; + hsp->mbox_db.dev = &pdev->dev; + hsp->mbox_db.ops = &tegra_hsp_db_ops; + + hsp->mbox_db.chans = devm_kcalloc(&pdev->dev, hsp->mbox_db.num_chans, + sizeof(*hsp->mbox_db.chans), + GFP_KERNEL); + if (!hsp->mbox_db.chans) + return -ENOMEM; + + if (hsp->doorbell_irq) { + err = tegra_hsp_add_doorbells(hsp); + if (err < 0) { + dev_err(&pdev->dev, "failed to add doorbells: %d\n", + err); + return err; + } + } + + err = devm_mbox_controller_register(&pdev->dev, &hsp->mbox_db); + if (err < 0) { + dev_err(&pdev->dev, "failed to register doorbell mailbox: %d\n", + err); + return err; + } + + /* setup the shared mailbox controller */ + hsp->mbox_sm.of_xlate = tegra_hsp_sm_xlate; + hsp->mbox_sm.num_chans = hsp->num_sm; + hsp->mbox_sm.dev = &pdev->dev; + hsp->mbox_sm.ops = &tegra_hsp_sm_ops; + + hsp->mbox_sm.chans = devm_kcalloc(&pdev->dev, hsp->mbox_sm.num_chans, + sizeof(*hsp->mbox_sm.chans), + GFP_KERNEL); + if (!hsp->mbox_sm.chans) + return -ENOMEM; + + if (hsp->shared_irqs) { + err = tegra_hsp_add_mailboxes(hsp, &pdev->dev); + if (err < 0) { + dev_err(&pdev->dev, "failed to add mailboxes: %d\n", + err); + return err; + } + } + + err = devm_mbox_controller_register(&pdev->dev, &hsp->mbox_sm); + if (err < 0) { + dev_err(&pdev->dev, "failed to register shared mailbox: %d\n", + err); + return err; + } + + platform_set_drvdata(pdev, hsp); + + if (hsp->doorbell_irq) { + err = devm_request_irq(&pdev->dev, hsp->doorbell_irq, + tegra_hsp_doorbell_irq, IRQF_NO_SUSPEND, + dev_name(&pdev->dev), hsp); + if (err < 0) + return err; + } + + if (hsp->shared_irqs) { + err = tegra_hsp_request_shared_irq(hsp); + if (err < 0) + return err; + } + + lockdep_register_key(&hsp->lock_key); + lockdep_set_class(&hsp->lock, &hsp->lock_key); + + return 0; +} + +static void tegra_hsp_remove(struct platform_device *pdev) +{ + struct tegra_hsp *hsp = platform_get_drvdata(pdev); + + lockdep_unregister_key(&hsp->lock_key); +} + +static int __maybe_unused tegra_hsp_resume(struct device *dev) +{ + struct tegra_hsp *hsp = dev_get_drvdata(dev); + unsigned int i; + struct tegra_hsp_doorbell *db; + + list_for_each_entry(db, &hsp->doorbells, list) { + if (db->channel.chan) + tegra_hsp_doorbell_startup(db->channel.chan); + } + + if (hsp->mailboxes) { + for (i = 0; i < hsp->num_sm; i++) { + struct tegra_hsp_mailbox *mb = &hsp->mailboxes[i]; + + if (mb->channel.chan->cl) + tegra_hsp_mailbox_startup(mb->channel.chan); + } + } + + return 0; +} + +static const struct dev_pm_ops tegra_hsp_pm_ops = { + .resume_noirq = tegra_hsp_resume, +}; + +static const struct tegra_hsp_db_map tegra186_hsp_db_map[] = { + { "ccplex", TEGRA_HSP_DB_MASTER_CCPLEX, HSP_DB_CCPLEX, }, + { "bpmp", TEGRA_HSP_DB_MASTER_BPMP, HSP_DB_BPMP, }, + { /* sentinel */ } +}; + +static const struct tegra_hsp_soc tegra186_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = false, + .has_128_bit_mb = false, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct tegra_hsp_soc tegra194_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = true, + .has_128_bit_mb = false, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct tegra_hsp_soc tegra234_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = false, + .has_128_bit_mb = true, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct tegra_hsp_soc tegra264_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = false, + .has_128_bit_mb = true, + .reg_stride = 0x1000, + .si_shift = 17, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0x1f, + .db_mask = 0x1f, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct of_device_id tegra_hsp_match[] = { + { .compatible = "nvidia,tegra186-hsp", .data = &tegra186_hsp_soc }, + { .compatible = "nvidia,tegra194-hsp", .data = &tegra194_hsp_soc }, + { .compatible = "nvidia,tegra234-hsp", .data = &tegra234_hsp_soc }, + { .compatible = "nvidia,tegra264-hsp", .data = &tegra264_hsp_soc }, + { } +}; + +static struct platform_driver tegra_hsp_driver = { + .driver = { + .name = "tegra-hsp", + .of_match_table = tegra_hsp_match, + .pm = &tegra_hsp_pm_ops, + }, + .probe = tegra_hsp_probe, + .remove = tegra_hsp_remove, +}; + +static int __init tegra_hsp_init(void) +{ + return platform_driver_register(&tegra_hsp_driver); +} +core_initcall(tegra_hsp_init); diff --git a/drivers/mailbox/ti-msgmgr.c b/drivers/mailbox/ti-msgmgr.c new file mode 100644 index 000000000..8eb8df8d9 --- /dev/null +++ b/drivers/mailbox/ti-msgmgr.c @@ -0,0 +1,932 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments' Message Manager Driver + * + * Copyright (C) 2015-2022 Texas Instruments Incorporated - https://www.ti.com/ + * Nishanth Menon + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/device.h> +#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/of_irq.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/soc/ti/ti-msgmgr.h> + +#define Q_DATA_OFFSET(proxy, queue, reg) \ + ((0x10000 * (proxy)) + (0x80 * (queue)) + ((reg) * 4)) +#define Q_STATE_OFFSET(queue) ((queue) * 0x4) +#define Q_STATE_ENTRY_COUNT_MASK (0xFFF000) + +#define SPROXY_THREAD_OFFSET(tid) (0x1000 * (tid)) +#define SPROXY_THREAD_DATA_OFFSET(tid, reg) \ + (SPROXY_THREAD_OFFSET(tid) + ((reg) * 0x4) + 0x4) + +#define SPROXY_THREAD_STATUS_OFFSET(tid) (SPROXY_THREAD_OFFSET(tid)) + +#define SPROXY_THREAD_STATUS_COUNT_MASK (0xFF) + +#define SPROXY_THREAD_CTRL_OFFSET(tid) (0x1000 + SPROXY_THREAD_OFFSET(tid)) +#define SPROXY_THREAD_CTRL_DIR_MASK (0x1 << 31) + +/** + * struct ti_msgmgr_valid_queue_desc - SoC valid queues meant for this processor + * @queue_id: Queue Number for this path + * @proxy_id: Proxy ID representing the processor in SoC + * @is_tx: Is this a receive path? + */ +struct ti_msgmgr_valid_queue_desc { + u8 queue_id; + u8 proxy_id; + bool is_tx; +}; + +/** + * struct ti_msgmgr_desc - Description of message manager integration + * @queue_count: Number of Queues + * @max_message_size: Message size in bytes + * @max_messages: Number of messages + * @data_first_reg: First data register for proxy data region + * @data_last_reg: Last data register for proxy data region + * @status_cnt_mask: Mask for getting the status value + * @status_err_mask: Mask for getting the error value, if applicable + * @tx_polled: Do I need to use polled mechanism for tx + * @tx_poll_timeout_ms: Timeout in ms if polled + * @valid_queues: List of Valid queues that the processor can access + * @data_region_name: Name of the proxy data region + * @status_region_name: Name of the proxy status region + * @ctrl_region_name: Name of the proxy control region + * @num_valid_queues: Number of valid queues + * @is_sproxy: Is this an Secure Proxy instance? + * + * This structure is used in of match data to describe how integration + * for a specific compatible SoC is done. + */ +struct ti_msgmgr_desc { + u8 queue_count; + u8 max_message_size; + u8 max_messages; + u8 data_first_reg; + u8 data_last_reg; + u32 status_cnt_mask; + u32 status_err_mask; + bool tx_polled; + int tx_poll_timeout_ms; + const struct ti_msgmgr_valid_queue_desc *valid_queues; + const char *data_region_name; + const char *status_region_name; + const char *ctrl_region_name; + int num_valid_queues; + bool is_sproxy; +}; + +/** + * struct ti_queue_inst - Description of a queue instance + * @name: Queue Name + * @queue_id: Queue Identifier as mapped on SoC + * @proxy_id: Proxy Identifier as mapped on SoC + * @irq: IRQ for Rx Queue + * @is_tx: 'true' if transmit queue, else, 'false' + * @queue_buff_start: First register of Data Buffer + * @queue_buff_end: Last (or confirmation) register of Data buffer + * @queue_state: Queue status register + * @queue_ctrl: Queue Control register + * @chan: Mailbox channel + * @rx_buff: Receive buffer pointer allocated at probe, max_message_size + * @polled_rx_mode: Use polling for rx instead of interrupts + */ +struct ti_queue_inst { + char name[30]; + u8 queue_id; + u8 proxy_id; + int irq; + bool is_tx; + void __iomem *queue_buff_start; + void __iomem *queue_buff_end; + void __iomem *queue_state; + void __iomem *queue_ctrl; + struct mbox_chan *chan; + u32 *rx_buff; + bool polled_rx_mode; +}; + +/** + * struct ti_msgmgr_inst - Description of a Message Manager Instance + * @dev: device pointer corresponding to the Message Manager instance + * @desc: Description of the SoC integration + * @queue_proxy_region: Queue proxy region where queue buffers are located + * @queue_state_debug_region: Queue status register regions + * @queue_ctrl_region: Queue Control register regions + * @num_valid_queues: Number of valid queues defined for the processor + * Note: other queues are probably reserved for other processors + * in the SoC. + * @qinsts: Array of valid Queue Instances for the Processor + * @mbox: Mailbox Controller + * @chans: Array for channels corresponding to the Queue Instances. + */ +struct ti_msgmgr_inst { + struct device *dev; + const struct ti_msgmgr_desc *desc; + void __iomem *queue_proxy_region; + void __iomem *queue_state_debug_region; + void __iomem *queue_ctrl_region; + u8 num_valid_queues; + struct ti_queue_inst *qinsts; + struct mbox_controller mbox; + struct mbox_chan *chans; +}; + +/** + * ti_msgmgr_queue_get_num_messages() - Get the number of pending messages + * @d: Description of message manager + * @qinst: Queue instance for which we check the number of pending messages + * + * Return: number of messages pending in the queue (0 == no pending messages) + */ +static inline int +ti_msgmgr_queue_get_num_messages(const struct ti_msgmgr_desc *d, + struct ti_queue_inst *qinst) +{ + u32 val; + u32 status_cnt_mask = d->status_cnt_mask; + + /* + * We cannot use relaxed operation here - update may happen + * real-time. + */ + val = readl(qinst->queue_state) & status_cnt_mask; + val >>= __ffs(status_cnt_mask); + + return val; +} + +/** + * ti_msgmgr_queue_is_error() - Check to see if there is queue error + * @d: Description of message manager + * @qinst: Queue instance for which we check the number of pending messages + * + * Return: true if error, else false + */ +static inline bool ti_msgmgr_queue_is_error(const struct ti_msgmgr_desc *d, + struct ti_queue_inst *qinst) +{ + u32 val; + + /* Msgmgr has no error detection */ + if (!d->is_sproxy) + return false; + + /* + * We cannot use relaxed operation here - update may happen + * real-time. + */ + val = readl(qinst->queue_state) & d->status_err_mask; + + return val ? true : false; +} + +static int ti_msgmgr_queue_rx_data(struct mbox_chan *chan, struct ti_queue_inst *qinst, + const struct ti_msgmgr_desc *desc) +{ + int num_words; + struct ti_msgmgr_message message; + void __iomem *data_reg; + u32 *word_data; + + /* + * I have no idea about the protocol being used to communicate with the + * remote producer - 0 could be valid data, so I wont make a judgement + * of how many bytes I should be reading. Let the client figure this + * out.. I just read the full message and pass it on.. + */ + message.len = desc->max_message_size; + message.buf = (u8 *)qinst->rx_buff; + + /* + * NOTE about register access involved here: + * the hardware block is implemented with 32bit access operations and no + * support for data splitting. We don't want the hardware to misbehave + * with sub 32bit access - For example: if the last register read is + * split into byte wise access, it can result in the queue getting + * stuck or indeterminate behavior. An out of order read operation may + * result in weird data results as well. + * Hence, we do not use memcpy_fromio or __ioread32_copy here, instead + * we depend on readl for the purpose. + * + * Also note that the final register read automatically marks the + * queue message as read. + */ + for (data_reg = qinst->queue_buff_start, word_data = qinst->rx_buff, + num_words = (desc->max_message_size / sizeof(u32)); + num_words; num_words--, data_reg += sizeof(u32), word_data++) + *word_data = readl(data_reg); + + /* + * Last register read automatically clears the IRQ if only 1 message + * is pending - so send the data up the stack.. + * NOTE: Client is expected to be as optimal as possible, since + * we invoke the handler in IRQ context. + */ + mbox_chan_received_data(chan, (void *)&message); + + return 0; +} + +static int ti_msgmgr_queue_rx_poll_timeout(struct mbox_chan *chan, int timeout_us) +{ + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst = chan->con_priv; + const struct ti_msgmgr_desc *desc = inst->desc; + int msg_count; + int ret; + + ret = readl_poll_timeout_atomic(qinst->queue_state, msg_count, + (msg_count & desc->status_cnt_mask), + 10, timeout_us); + if (ret != 0) + return ret; + + ti_msgmgr_queue_rx_data(chan, qinst, desc); + + return 0; +} + +/** + * ti_msgmgr_queue_rx_interrupt() - Interrupt handler for receive Queue + * @irq: Interrupt number + * @p: Channel Pointer + * + * Return: -EINVAL if there is no instance + * IRQ_NONE if the interrupt is not ours. + * IRQ_HANDLED if the rx interrupt was successfully handled. + */ +static irqreturn_t ti_msgmgr_queue_rx_interrupt(int irq, void *p) +{ + struct mbox_chan *chan = p; + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst = chan->con_priv; + const struct ti_msgmgr_desc *desc; + int msg_count; + + if (WARN_ON(!inst)) { + dev_err(dev, "no platform drv data??\n"); + return -EINVAL; + } + + /* Do I have an invalid interrupt source? */ + if (qinst->is_tx) { + dev_err(dev, "Cannot handle rx interrupt on tx channel %s\n", + qinst->name); + return IRQ_NONE; + } + + desc = inst->desc; + if (ti_msgmgr_queue_is_error(desc, qinst)) { + dev_err(dev, "Error on Rx channel %s\n", qinst->name); + return IRQ_NONE; + } + + /* Do I actually have messages to read? */ + msg_count = ti_msgmgr_queue_get_num_messages(desc, qinst); + if (!msg_count) { + /* Shared IRQ? */ + dev_dbg(dev, "Spurious event - 0 pending data!\n"); + return IRQ_NONE; + } + + ti_msgmgr_queue_rx_data(chan, qinst, desc); + + return IRQ_HANDLED; +} + +/** + * ti_msgmgr_queue_peek_data() - Peek to see if there are any rx messages. + * @chan: Channel Pointer + * + * Return: 'true' if there is pending rx data, 'false' if there is none. + */ +static bool ti_msgmgr_queue_peek_data(struct mbox_chan *chan) +{ + struct ti_queue_inst *qinst = chan->con_priv; + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + const struct ti_msgmgr_desc *desc = inst->desc; + int msg_count; + + if (qinst->is_tx) + return false; + + if (ti_msgmgr_queue_is_error(desc, qinst)) { + dev_err(dev, "Error on channel %s\n", qinst->name); + return false; + } + + msg_count = ti_msgmgr_queue_get_num_messages(desc, qinst); + + return msg_count ? true : false; +} + +/** + * ti_msgmgr_last_tx_done() - See if all the tx messages are sent + * @chan: Channel pointer + * + * Return: 'true' is no pending tx data, 'false' if there are any. + */ +static bool ti_msgmgr_last_tx_done(struct mbox_chan *chan) +{ + struct ti_queue_inst *qinst = chan->con_priv; + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + const struct ti_msgmgr_desc *desc = inst->desc; + int msg_count; + + if (!qinst->is_tx) + return false; + + if (ti_msgmgr_queue_is_error(desc, qinst)) { + dev_err(dev, "Error on channel %s\n", qinst->name); + return false; + } + + msg_count = ti_msgmgr_queue_get_num_messages(desc, qinst); + + if (desc->is_sproxy) { + /* In secure proxy, msg_count indicates how many we can send */ + return msg_count ? true : false; + } + + /* if we have any messages pending.. */ + return msg_count ? false : true; +} + +static bool ti_msgmgr_chan_has_polled_queue_rx(struct mbox_chan *chan) +{ + struct ti_queue_inst *qinst; + + if (!chan) + return false; + + qinst = chan->con_priv; + return qinst->polled_rx_mode; +} + +/** + * ti_msgmgr_send_data() - Send data + * @chan: Channel Pointer + * @data: ti_msgmgr_message * Message Pointer + * + * Return: 0 if all goes good, else appropriate error messages. + */ +static int ti_msgmgr_send_data(struct mbox_chan *chan, void *data) +{ + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + const struct ti_msgmgr_desc *desc; + struct ti_queue_inst *qinst = chan->con_priv; + int num_words, trail_bytes; + struct ti_msgmgr_message *message = data; + void __iomem *data_reg; + u32 *word_data; + int ret = 0; + + if (WARN_ON(!inst)) { + dev_err(dev, "no platform drv data??\n"); + return -EINVAL; + } + desc = inst->desc; + + if (ti_msgmgr_queue_is_error(desc, qinst)) { + dev_err(dev, "Error on channel %s\n", qinst->name); + return false; + } + + if (desc->max_message_size < message->len) { + dev_err(dev, "Queue %s message length %zu > max %d\n", + qinst->name, message->len, desc->max_message_size); + return -EINVAL; + } + + /* NOTE: Constraints similar to rx path exists here as well */ + for (data_reg = qinst->queue_buff_start, + num_words = message->len / sizeof(u32), + word_data = (u32 *)message->buf; + num_words; num_words--, data_reg += sizeof(u32), word_data++) + writel(*word_data, data_reg); + + trail_bytes = message->len % sizeof(u32); + if (trail_bytes) { + u32 data_trail = *word_data; + + /* Ensure all unused data is 0 */ + data_trail &= 0xFFFFFFFF >> (8 * (sizeof(u32) - trail_bytes)); + writel(data_trail, data_reg); + data_reg += sizeof(u32); + } + + /* + * 'data_reg' indicates next register to write. If we did not already + * write on tx complete reg(last reg), we must do so for transmit + * In addition, we also need to make sure all intermediate data + * registers(if any required), are reset to 0 for TISCI backward + * compatibility to be maintained. + */ + while (data_reg <= qinst->queue_buff_end) { + writel(0, data_reg); + data_reg += sizeof(u32); + } + + /* If we are in polled mode, wait for a response before proceeding */ + if (ti_msgmgr_chan_has_polled_queue_rx(message->chan_rx)) + ret = ti_msgmgr_queue_rx_poll_timeout(message->chan_rx, + message->timeout_rx_ms * 1000); + + return ret; +} + +/** + * ti_msgmgr_queue_rx_irq_req() - RX IRQ request + * @dev: device pointer + * @d: descriptor for ti_msgmgr + * @qinst: Queue instance + * @chan: Channel pointer + */ +static int ti_msgmgr_queue_rx_irq_req(struct device *dev, + const struct ti_msgmgr_desc *d, + struct ti_queue_inst *qinst, + struct mbox_chan *chan) +{ + int ret = 0; + char of_rx_irq_name[7]; + struct device_node *np; + + snprintf(of_rx_irq_name, sizeof(of_rx_irq_name), + "rx_%03d", d->is_sproxy ? qinst->proxy_id : qinst->queue_id); + + /* Get the IRQ if not found */ + if (qinst->irq < 0) { + np = of_node_get(dev->of_node); + if (!np) + return -ENODATA; + qinst->irq = of_irq_get_byname(np, of_rx_irq_name); + of_node_put(np); + + if (qinst->irq < 0) { + dev_err(dev, + "QID %d PID %d:No IRQ[%s]: %d\n", + qinst->queue_id, qinst->proxy_id, + of_rx_irq_name, qinst->irq); + return qinst->irq; + } + } + + /* With the expectation that the IRQ might be shared in SoC */ + ret = request_irq(qinst->irq, ti_msgmgr_queue_rx_interrupt, + IRQF_SHARED, qinst->name, chan); + if (ret) { + dev_err(dev, "Unable to get IRQ %d on %s(res=%d)\n", + qinst->irq, qinst->name, ret); + } + + return ret; +} + +/** + * ti_msgmgr_queue_startup() - Startup queue + * @chan: Channel pointer + * + * Return: 0 if all goes good, else return corresponding error message + */ +static int ti_msgmgr_queue_startup(struct mbox_chan *chan) +{ + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst = chan->con_priv; + const struct ti_msgmgr_desc *d = inst->desc; + int ret; + int msg_count; + + /* + * If sproxy is starting and can send messages, we are a Tx thread, + * else Rx + */ + if (d->is_sproxy) { + qinst->is_tx = (readl(qinst->queue_ctrl) & + SPROXY_THREAD_CTRL_DIR_MASK) ? false : true; + + msg_count = ti_msgmgr_queue_get_num_messages(d, qinst); + + if (!msg_count && qinst->is_tx) { + dev_err(dev, "%s: Cannot transmit with 0 credits!\n", + qinst->name); + return -EINVAL; + } + } + + if (!qinst->is_tx) { + /* Allocate usage buffer for rx */ + qinst->rx_buff = kzalloc(d->max_message_size, GFP_KERNEL); + if (!qinst->rx_buff) + return -ENOMEM; + /* Request IRQ */ + ret = ti_msgmgr_queue_rx_irq_req(dev, d, qinst, chan); + if (ret) { + kfree(qinst->rx_buff); + return ret; + } + } + + return 0; +} + +/** + * ti_msgmgr_queue_shutdown() - Shutdown the queue + * @chan: Channel pointer + */ +static void ti_msgmgr_queue_shutdown(struct mbox_chan *chan) +{ + struct ti_queue_inst *qinst = chan->con_priv; + + if (!qinst->is_tx) { + free_irq(qinst->irq, chan); + kfree(qinst->rx_buff); + } +} + +/** + * ti_msgmgr_of_xlate() - Translation of phandle to queue + * @mbox: Mailbox controller + * @p: phandle pointer + * + * Return: Mailbox channel corresponding to the queue, else return error + * pointer. + */ +static struct mbox_chan *ti_msgmgr_of_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *p) +{ + struct ti_msgmgr_inst *inst; + int req_qid, req_pid; + struct ti_queue_inst *qinst; + const struct ti_msgmgr_desc *d; + int i, ncells; + + inst = container_of(mbox, struct ti_msgmgr_inst, mbox); + if (WARN_ON(!inst)) + return ERR_PTR(-EINVAL); + + d = inst->desc; + + if (d->is_sproxy) + ncells = 1; + else + ncells = 2; + if (p->args_count != ncells) { + dev_err(inst->dev, "Invalid arguments in dt[%d]. Must be %d\n", + p->args_count, ncells); + return ERR_PTR(-EINVAL); + } + if (ncells == 1) { + req_qid = 0; + req_pid = p->args[0]; + } else { + req_qid = p->args[0]; + req_pid = p->args[1]; + } + + if (d->is_sproxy) { + if (req_pid >= d->num_valid_queues) + goto err; + qinst = &inst->qinsts[req_pid]; + return qinst->chan; + } + + for (qinst = inst->qinsts, i = 0; i < inst->num_valid_queues; + i++, qinst++) { + if (req_qid == qinst->queue_id && req_pid == qinst->proxy_id) + return qinst->chan; + } + +err: + dev_err(inst->dev, "Queue ID %d, Proxy ID %d is wrong on %pOFn\n", + req_qid, req_pid, p->np); + return ERR_PTR(-ENOENT); +} + +/** + * ti_msgmgr_queue_setup() - Setup data structures for each queue instance + * @idx: index of the queue + * @dev: pointer to the message manager device + * @np: pointer to the of node + * @inst: Queue instance pointer + * @d: Message Manager instance description data + * @qd: Queue description data + * @qinst: Queue instance pointer + * @chan: pointer to mailbox channel + * + * Return: 0 if all went well, else return corresponding error + */ +static int ti_msgmgr_queue_setup(int idx, struct device *dev, + struct device_node *np, + struct ti_msgmgr_inst *inst, + const struct ti_msgmgr_desc *d, + const struct ti_msgmgr_valid_queue_desc *qd, + struct ti_queue_inst *qinst, + struct mbox_chan *chan) +{ + char *dir; + + qinst->proxy_id = qd->proxy_id; + qinst->queue_id = qd->queue_id; + + if (qinst->queue_id > d->queue_count) { + dev_err(dev, "Queue Data [idx=%d] queuid %d > %d\n", + idx, qinst->queue_id, d->queue_count); + return -ERANGE; + } + + if (d->is_sproxy) { + qinst->queue_buff_start = inst->queue_proxy_region + + SPROXY_THREAD_DATA_OFFSET(qinst->proxy_id, + d->data_first_reg); + qinst->queue_buff_end = inst->queue_proxy_region + + SPROXY_THREAD_DATA_OFFSET(qinst->proxy_id, + d->data_last_reg); + qinst->queue_state = inst->queue_state_debug_region + + SPROXY_THREAD_STATUS_OFFSET(qinst->proxy_id); + qinst->queue_ctrl = inst->queue_ctrl_region + + SPROXY_THREAD_CTRL_OFFSET(qinst->proxy_id); + + /* XXX: DONOT read registers here!.. Some may be unusable */ + dir = "thr"; + snprintf(qinst->name, sizeof(qinst->name), "%s %s_%03d", + dev_name(dev), dir, qinst->proxy_id); + } else { + qinst->queue_buff_start = inst->queue_proxy_region + + Q_DATA_OFFSET(qinst->proxy_id, qinst->queue_id, + d->data_first_reg); + qinst->queue_buff_end = inst->queue_proxy_region + + Q_DATA_OFFSET(qinst->proxy_id, qinst->queue_id, + d->data_last_reg); + qinst->queue_state = + inst->queue_state_debug_region + + Q_STATE_OFFSET(qinst->queue_id); + qinst->is_tx = qd->is_tx; + dir = qinst->is_tx ? "tx" : "rx"; + snprintf(qinst->name, sizeof(qinst->name), "%s %s_%03d_%03d", + dev_name(dev), dir, qinst->queue_id, qinst->proxy_id); + } + + qinst->chan = chan; + + /* Setup an error value for IRQ - Lazy allocation */ + qinst->irq = -EINVAL; + + chan->con_priv = qinst; + + dev_dbg(dev, "[%d] qidx=%d pidx=%d irq=%d q_s=%p q_e = %p\n", + idx, qinst->queue_id, qinst->proxy_id, qinst->irq, + qinst->queue_buff_start, qinst->queue_buff_end); + return 0; +} + +static int ti_msgmgr_queue_rx_set_polled_mode(struct ti_queue_inst *qinst, bool enable) +{ + if (enable) { + disable_irq(qinst->irq); + qinst->polled_rx_mode = true; + } else { + enable_irq(qinst->irq); + qinst->polled_rx_mode = false; + } + + return 0; +} + +static int ti_msgmgr_suspend(struct device *dev) +{ + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst; + int i; + + /* + * We must switch operation to polled mode now as drivers and the genpd + * layer may make late TI SCI calls to change clock and device states + * from the noirq phase of suspend. + */ + for (qinst = inst->qinsts, i = 0; i < inst->num_valid_queues; qinst++, i++) { + if (!qinst->is_tx) + ti_msgmgr_queue_rx_set_polled_mode(qinst, true); + } + + return 0; +} + +static int ti_msgmgr_resume(struct device *dev) +{ + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst; + int i; + + for (qinst = inst->qinsts, i = 0; i < inst->num_valid_queues; qinst++, i++) { + if (!qinst->is_tx) + ti_msgmgr_queue_rx_set_polled_mode(qinst, false); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ti_msgmgr_pm_ops, ti_msgmgr_suspend, ti_msgmgr_resume); + +/* Queue operations */ +static const struct mbox_chan_ops ti_msgmgr_chan_ops = { + .startup = ti_msgmgr_queue_startup, + .shutdown = ti_msgmgr_queue_shutdown, + .peek_data = ti_msgmgr_queue_peek_data, + .last_tx_done = ti_msgmgr_last_tx_done, + .send_data = ti_msgmgr_send_data, +}; + +/* Keystone K2G SoC integration details */ +static const struct ti_msgmgr_valid_queue_desc k2g_valid_queues[] = { + {.queue_id = 0, .proxy_id = 0, .is_tx = true,}, + {.queue_id = 1, .proxy_id = 0, .is_tx = true,}, + {.queue_id = 2, .proxy_id = 0, .is_tx = true,}, + {.queue_id = 3, .proxy_id = 0, .is_tx = true,}, + {.queue_id = 5, .proxy_id = 2, .is_tx = false,}, + {.queue_id = 56, .proxy_id = 1, .is_tx = true,}, + {.queue_id = 57, .proxy_id = 2, .is_tx = false,}, + {.queue_id = 58, .proxy_id = 3, .is_tx = true,}, + {.queue_id = 59, .proxy_id = 4, .is_tx = true,}, + {.queue_id = 60, .proxy_id = 5, .is_tx = true,}, + {.queue_id = 61, .proxy_id = 6, .is_tx = true,}, +}; + +static const struct ti_msgmgr_desc k2g_desc = { + .queue_count = 64, + .max_message_size = 64, + .max_messages = 128, + .data_region_name = "queue_proxy_region", + .status_region_name = "queue_state_debug_region", + .data_first_reg = 16, + .data_last_reg = 31, + .status_cnt_mask = Q_STATE_ENTRY_COUNT_MASK, + .tx_polled = false, + .valid_queues = k2g_valid_queues, + .num_valid_queues = ARRAY_SIZE(k2g_valid_queues), + .is_sproxy = false, +}; + +static const struct ti_msgmgr_desc am654_desc = { + .queue_count = 190, + .num_valid_queues = 190, + .max_message_size = 60, + .data_region_name = "target_data", + .status_region_name = "rt", + .ctrl_region_name = "scfg", + .data_first_reg = 0, + .data_last_reg = 14, + .status_cnt_mask = SPROXY_THREAD_STATUS_COUNT_MASK, + .tx_polled = false, + .is_sproxy = true, +}; + +static const struct of_device_id ti_msgmgr_of_match[] = { + {.compatible = "ti,k2g-message-manager", .data = &k2g_desc}, + {.compatible = "ti,am654-secure-proxy", .data = &am654_desc}, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, ti_msgmgr_of_match); + +static int ti_msgmgr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np; + const struct ti_msgmgr_desc *desc; + struct ti_msgmgr_inst *inst; + struct ti_queue_inst *qinst; + struct mbox_controller *mbox; + struct mbox_chan *chans; + int queue_count; + int i; + int ret = -EINVAL; + const struct ti_msgmgr_valid_queue_desc *queue_desc; + + if (!dev->of_node) { + dev_err(dev, "no OF information\n"); + return -EINVAL; + } + np = dev->of_node; + + inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->dev = dev; + inst->desc = desc = device_get_match_data(dev); + + inst->queue_proxy_region = + devm_platform_ioremap_resource_byname(pdev, desc->data_region_name); + if (IS_ERR(inst->queue_proxy_region)) + return PTR_ERR(inst->queue_proxy_region); + + inst->queue_state_debug_region = + devm_platform_ioremap_resource_byname(pdev, desc->status_region_name); + if (IS_ERR(inst->queue_state_debug_region)) + return PTR_ERR(inst->queue_state_debug_region); + + if (desc->is_sproxy) { + inst->queue_ctrl_region = + devm_platform_ioremap_resource_byname(pdev, desc->ctrl_region_name); + if (IS_ERR(inst->queue_ctrl_region)) + return PTR_ERR(inst->queue_ctrl_region); + } + + dev_dbg(dev, "proxy region=%p, queue_state=%p\n", + inst->queue_proxy_region, inst->queue_state_debug_region); + + queue_count = desc->num_valid_queues; + if (!queue_count || queue_count > desc->queue_count) { + dev_crit(dev, "Invalid Number of queues %d. Max %d\n", + queue_count, desc->queue_count); + return -ERANGE; + } + inst->num_valid_queues = queue_count; + + qinst = devm_kcalloc(dev, queue_count, sizeof(*qinst), GFP_KERNEL); + if (!qinst) + return -ENOMEM; + inst->qinsts = qinst; + + chans = devm_kcalloc(dev, queue_count, sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + inst->chans = chans; + + if (desc->is_sproxy) { + struct ti_msgmgr_valid_queue_desc sproxy_desc; + + /* All proxies may be valid in Secure Proxy instance */ + for (i = 0; i < queue_count; i++, qinst++, chans++) { + sproxy_desc.queue_id = 0; + sproxy_desc.proxy_id = i; + ret = ti_msgmgr_queue_setup(i, dev, np, inst, + desc, &sproxy_desc, qinst, + chans); + if (ret) + return ret; + } + } else { + /* Only Some proxies are valid in Message Manager */ + for (i = 0, queue_desc = desc->valid_queues; + i < queue_count; i++, qinst++, chans++, queue_desc++) { + ret = ti_msgmgr_queue_setup(i, dev, np, inst, + desc, queue_desc, qinst, + chans); + if (ret) + return ret; + } + } + + mbox = &inst->mbox; + mbox->dev = dev; + mbox->ops = &ti_msgmgr_chan_ops; + mbox->chans = inst->chans; + mbox->num_chans = inst->num_valid_queues; + mbox->txdone_irq = false; + mbox->txdone_poll = desc->tx_polled; + if (desc->tx_polled) + mbox->txpoll_period = desc->tx_poll_timeout_ms; + mbox->of_xlate = ti_msgmgr_of_xlate; + + platform_set_drvdata(pdev, inst); + ret = devm_mbox_controller_register(dev, mbox); + if (ret) + dev_err(dev, "Failed to register mbox_controller(%d)\n", ret); + + return ret; +} + +static struct platform_driver ti_msgmgr_driver = { + .probe = ti_msgmgr_probe, + .driver = { + .name = "ti-msgmgr", + .of_match_table = ti_msgmgr_of_match, + .pm = &ti_msgmgr_pm_ops, + }, +}; +module_platform_driver(ti_msgmgr_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TI message manager driver"); +MODULE_AUTHOR("Nishanth Menon"); +MODULE_ALIAS("platform:ti-msgmgr"); diff --git a/drivers/mailbox/zynqmp-ipi-mailbox.c b/drivers/mailbox/zynqmp-ipi-mailbox.c new file mode 100644 index 000000000..42d2583e4 --- /dev/null +++ b/drivers/mailbox/zynqmp-ipi-mailbox.c @@ -0,0 +1,1040 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Inter Processor Interrupt(IPI) Mailbox Driver + * + * Copyright (C) 2018 Xilinx, Inc. + */ + +#include <linux/arm-smccc.h> +#include <linux/cpuhotplug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mailbox_controller.h> +#include <linux/mailbox/zynqmp-ipi-message.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> + +/* IPI agent ID any */ +#define IPI_ID_ANY 0xFFUL + +/* indicate if ZynqMP IPI mailbox driver uses SMC calls or HVC calls */ +#define USE_SMC 0 +#define USE_HVC 1 + +/* Default IPI SMC function IDs */ +#define SMC_IPI_MAILBOX_OPEN 0x82001000U +#define SMC_IPI_MAILBOX_RELEASE 0x82001001U +#define SMC_IPI_MAILBOX_STATUS_ENQUIRY 0x82001002U +#define SMC_IPI_MAILBOX_NOTIFY 0x82001003U +#define SMC_IPI_MAILBOX_ACK 0x82001004U +#define SMC_IPI_MAILBOX_ENABLE_IRQ 0x82001005U +#define SMC_IPI_MAILBOX_DISABLE_IRQ 0x82001006U + +/* IPI SMC Macros */ +#define IPI_SMC_ENQUIRY_DIRQ_MASK 0x00000001UL /* Flag to indicate if + * notification interrupt + * to be disabled. + */ +#define IPI_SMC_ACK_EIRQ_MASK 0x00000001UL /* Flag to indicate if + * notification interrupt + * to be enabled. + */ + +/* IPI mailbox status */ +#define IPI_MB_STATUS_IDLE 0 +#define IPI_MB_STATUS_SEND_PENDING 1 +#define IPI_MB_STATUS_RECV_PENDING 2 + +#define IPI_MB_CHNL_TX 0 /* IPI mailbox TX channel */ +#define IPI_MB_CHNL_RX 1 /* IPI mailbox RX channel */ + +/* IPI Message Buffer Information */ +#define RESP_OFFSET 0x20U +#define DEST_OFFSET 0x40U +#define IPI_BUF_SIZE 0x20U +#define DST_BIT_POS 9U +#define SRC_BITMASK GENMASK(11, 8) + +/* Macro to represent SGI type for IPI IRQs */ +#define IPI_IRQ_TYPE_SGI 2 + +/* + * Module parameters + */ +static int tx_poll_period = 5; +module_param_named(tx_poll_period, tx_poll_period, int, 0644); +MODULE_PARM_DESC(tx_poll_period, "Poll period waiting for ack after send."); + +/** + * struct zynqmp_ipi_mchan - Description of a Xilinx ZynqMP IPI mailbox channel + * @is_opened: indicate if the IPI channel is opened + * @req_buf: local to remote request buffer start address + * @resp_buf: local to remote response buffer start address + * @req_buf_size: request buffer size + * @resp_buf_size: response buffer size + * @rx_buf: receive buffer to pass received message to client + * @chan_type: channel type + */ +struct zynqmp_ipi_mchan { + int is_opened; + void __iomem *req_buf; + void __iomem *resp_buf; + void *rx_buf; + size_t req_buf_size; + size_t resp_buf_size; + unsigned int chan_type; +}; + +struct zynqmp_ipi_mbox; + +typedef int (*setup_ipi_fn)(struct zynqmp_ipi_mbox *ipi_mbox, struct device_node *node); + +/** + * struct zynqmp_ipi_mbox - Description of a ZynqMP IPI mailbox + * platform data. + * @pdata: pointer to the IPI private data + * @dev: device pointer corresponding to the Xilinx ZynqMP + * IPI mailbox + * @remote_id: remote IPI agent ID + * @mbox: mailbox Controller + * @mchans: array for channels, tx channel and rx channel. + * @setup_ipi_fn: Function Pointer to set up IPI Channels + */ +struct zynqmp_ipi_mbox { + struct zynqmp_ipi_pdata *pdata; + struct device dev; + u32 remote_id; + struct mbox_controller mbox; + struct zynqmp_ipi_mchan mchans[2]; + setup_ipi_fn setup_ipi_fn; +}; + +/** + * struct zynqmp_ipi_pdata - Description of z ZynqMP IPI agent platform data. + * + * @dev: device pointer corresponding to the Xilinx ZynqMP + * IPI agent + * @irq: IPI agent interrupt ID + * @irq_type: IPI SGI or SPI IRQ type + * @method: IPI SMC or HVC is going to be used + * @local_id: local IPI agent ID + * @virq_sgi: IRQ number mapped to SGI + * @num_mboxes: number of mailboxes of this IPI agent + * @ipi_mboxes: IPI mailboxes of this IPI agent + */ +struct zynqmp_ipi_pdata { + struct device *dev; + int irq; + unsigned int irq_type; + unsigned int method; + u32 local_id; + int virq_sgi; + int num_mboxes; + struct zynqmp_ipi_mbox ipi_mboxes[] __counted_by(num_mboxes); +}; + +static DEFINE_PER_CPU(struct zynqmp_ipi_pdata *, per_cpu_pdata); + +static struct device_driver zynqmp_ipi_mbox_driver = { + .owner = THIS_MODULE, + .name = "zynqmp-ipi-mbox", +}; + +static void zynqmp_ipi_fw_call(struct zynqmp_ipi_mbox *ipi_mbox, + unsigned long a0, unsigned long a3, + struct arm_smccc_res *res) +{ + struct zynqmp_ipi_pdata *pdata = ipi_mbox->pdata; + unsigned long a1, a2; + + a1 = pdata->local_id; + a2 = ipi_mbox->remote_id; + if (pdata->method == USE_SMC) + arm_smccc_smc(a0, a1, a2, a3, 0, 0, 0, 0, res); + else + arm_smccc_hvc(a0, a1, a2, a3, 0, 0, 0, 0, res); +} + +/** + * zynqmp_ipi_interrupt - Interrupt handler for IPI notification + * + * @irq: Interrupt number + * @data: ZynqMP IPI mailbox platform data. + * + * Return: -EINVAL if there is no instance + * IRQ_NONE if the interrupt is not ours. + * IRQ_HANDLED if the rx interrupt was successfully handled. + */ +static irqreturn_t zynqmp_ipi_interrupt(int irq, void *data) +{ + struct zynqmp_ipi_pdata *pdata = data; + struct mbox_chan *chan; + struct zynqmp_ipi_mbox *ipi_mbox; + struct zynqmp_ipi_mchan *mchan; + struct zynqmp_ipi_message *msg; + u64 arg0, arg3; + struct arm_smccc_res res; + int ret, i, status = IRQ_NONE; + + (void)irq; + arg0 = SMC_IPI_MAILBOX_STATUS_ENQUIRY; + arg3 = IPI_SMC_ENQUIRY_DIRQ_MASK; + for (i = 0; i < pdata->num_mboxes; i++) { + ipi_mbox = &pdata->ipi_mboxes[i]; + mchan = &ipi_mbox->mchans[IPI_MB_CHNL_RX]; + chan = &ipi_mbox->mbox.chans[IPI_MB_CHNL_RX]; + zynqmp_ipi_fw_call(ipi_mbox, arg0, arg3, &res); + ret = (int)(res.a0 & 0xFFFFFFFF); + if (ret > 0 && ret & IPI_MB_STATUS_RECV_PENDING) { + if (mchan->is_opened) { + msg = mchan->rx_buf; + if (msg) { + msg->len = mchan->req_buf_size; + memcpy_fromio(msg->data, mchan->req_buf, + msg->len); + } + mbox_chan_received_data(chan, (void *)msg); + status = IRQ_HANDLED; + } + } + } + return status; +} + +static irqreturn_t zynqmp_sgi_interrupt(int irq, void *data) +{ + struct zynqmp_ipi_pdata **pdata_ptr = data; + struct zynqmp_ipi_pdata *pdata = *pdata_ptr; + + return zynqmp_ipi_interrupt(irq, pdata); +} + +/** + * zynqmp_ipi_peek_data - Peek to see if there are any rx messages. + * + * @chan: Channel Pointer + * + * Return: 'true' if there is pending rx data, 'false' if there is none. + */ +static bool zynqmp_ipi_peek_data(struct mbox_chan *chan) +{ + struct device *dev = chan->mbox->dev; + struct zynqmp_ipi_mbox *ipi_mbox = dev_get_drvdata(dev); + struct zynqmp_ipi_mchan *mchan = chan->con_priv; + int ret; + u64 arg0; + struct arm_smccc_res res; + + if (WARN_ON(!ipi_mbox)) { + dev_err(dev, "no platform drv data??\n"); + return false; + } + + arg0 = SMC_IPI_MAILBOX_STATUS_ENQUIRY; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + ret = (int)(res.a0 & 0xFFFFFFFF); + + if (mchan->chan_type == IPI_MB_CHNL_TX) { + /* TX channel, check if the message has been acked + * by the remote, if yes, response is available. + */ + if (ret < 0 || ret & IPI_MB_STATUS_SEND_PENDING) + return false; + else + return true; + } else if (ret > 0 && ret & IPI_MB_STATUS_RECV_PENDING) { + /* RX channel, check if there is message arrived. */ + return true; + } + return false; +} + +/** + * zynqmp_ipi_last_tx_done - See if the last tx message is sent + * + * @chan: Channel pointer + * + * Return: 'true' is no pending tx data, 'false' if there are any. + */ +static bool zynqmp_ipi_last_tx_done(struct mbox_chan *chan) +{ + struct device *dev = chan->mbox->dev; + struct zynqmp_ipi_mbox *ipi_mbox = dev_get_drvdata(dev); + struct zynqmp_ipi_mchan *mchan = chan->con_priv; + int ret; + u64 arg0; + struct arm_smccc_res res; + + if (WARN_ON(!ipi_mbox)) { + dev_err(dev, "no platform drv data??\n"); + return false; + } + + if (mchan->chan_type == IPI_MB_CHNL_TX) { + /* We only need to check if the message been taken + * by the remote in the TX channel + */ + arg0 = SMC_IPI_MAILBOX_STATUS_ENQUIRY; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + /* Check the SMC call status, a0 of the result */ + ret = (int)(res.a0 & 0xFFFFFFFF); + if (ret < 0 || ret & IPI_MB_STATUS_SEND_PENDING) + return false; + return true; + } + /* Always true for the response message in RX channel */ + return true; +} + +/** + * zynqmp_ipi_send_data - Send data + * + * @chan: Channel Pointer + * @data: Message Pointer + * + * Return: 0 if all goes good, else appropriate error messages. + */ +static int zynqmp_ipi_send_data(struct mbox_chan *chan, void *data) +{ + struct device *dev = chan->mbox->dev; + struct zynqmp_ipi_mbox *ipi_mbox = dev_get_drvdata(dev); + struct zynqmp_ipi_mchan *mchan = chan->con_priv; + struct zynqmp_ipi_message *msg = data; + u64 arg0; + struct arm_smccc_res res; + + if (WARN_ON(!ipi_mbox)) { + dev_err(dev, "no platform drv data??\n"); + return -EINVAL; + } + + if (mchan->chan_type == IPI_MB_CHNL_TX) { + /* Send request message */ + if (msg && msg->len > mchan->req_buf_size && mchan->req_buf) { + dev_err(dev, "channel %d message length %u > max %lu\n", + mchan->chan_type, (unsigned int)msg->len, + mchan->req_buf_size); + return -EINVAL; + } + if (msg && msg->len && mchan->req_buf) + memcpy_toio(mchan->req_buf, msg->data, msg->len); + /* Kick IPI mailbox to send message */ + arg0 = SMC_IPI_MAILBOX_NOTIFY; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + } else { + /* Send response message */ + if (msg && msg->len > mchan->resp_buf_size && mchan->resp_buf) { + dev_err(dev, "channel %d message length %u > max %lu\n", + mchan->chan_type, (unsigned int)msg->len, + mchan->resp_buf_size); + return -EINVAL; + } + if (msg && msg->len && mchan->resp_buf) + memcpy_toio(mchan->resp_buf, msg->data, msg->len); + arg0 = SMC_IPI_MAILBOX_ACK; + zynqmp_ipi_fw_call(ipi_mbox, arg0, IPI_SMC_ACK_EIRQ_MASK, + &res); + } + return 0; +} + +/** + * zynqmp_ipi_startup - Startup the IPI channel + * + * @chan: Channel pointer + * + * Return: 0 if all goes good, else return corresponding error message + */ +static int zynqmp_ipi_startup(struct mbox_chan *chan) +{ + struct device *dev = chan->mbox->dev; + struct zynqmp_ipi_mbox *ipi_mbox = dev_get_drvdata(dev); + struct zynqmp_ipi_mchan *mchan = chan->con_priv; + u64 arg0; + struct arm_smccc_res res; + int ret = 0; + unsigned int nchan_type; + + if (mchan->is_opened) + return 0; + + /* If no channel has been opened, open the IPI mailbox */ + nchan_type = (mchan->chan_type + 1) % 2; + if (!ipi_mbox->mchans[nchan_type].is_opened) { + arg0 = SMC_IPI_MAILBOX_OPEN; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + /* Check the SMC call status, a0 of the result */ + ret = (int)(res.a0 & 0xFFFFFFFF); + if (ret < 0) { + dev_err(dev, "SMC to open the IPI channel failed.\n"); + return ret; + } + ret = 0; + } + + /* If it is RX channel, enable the IPI notification interrupt */ + if (mchan->chan_type == IPI_MB_CHNL_RX) { + arg0 = SMC_IPI_MAILBOX_ENABLE_IRQ; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + } + mchan->is_opened = 1; + + return ret; +} + +/** + * zynqmp_ipi_shutdown - Shutdown the IPI channel + * + * @chan: Channel pointer + */ +static void zynqmp_ipi_shutdown(struct mbox_chan *chan) +{ + struct device *dev = chan->mbox->dev; + struct zynqmp_ipi_mbox *ipi_mbox = dev_get_drvdata(dev); + struct zynqmp_ipi_mchan *mchan = chan->con_priv; + u64 arg0; + struct arm_smccc_res res; + unsigned int chan_type; + + if (!mchan->is_opened) + return; + + /* If it is RX channel, disable notification interrupt */ + chan_type = mchan->chan_type; + if (chan_type == IPI_MB_CHNL_RX) { + arg0 = SMC_IPI_MAILBOX_DISABLE_IRQ; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + } + /* Release IPI mailbox if no other channel is opened */ + chan_type = (chan_type + 1) % 2; + if (!ipi_mbox->mchans[chan_type].is_opened) { + arg0 = SMC_IPI_MAILBOX_RELEASE; + zynqmp_ipi_fw_call(ipi_mbox, arg0, 0, &res); + } + + mchan->is_opened = 0; +} + +/* ZynqMP IPI mailbox operations */ +static const struct mbox_chan_ops zynqmp_ipi_chan_ops = { + .startup = zynqmp_ipi_startup, + .shutdown = zynqmp_ipi_shutdown, + .peek_data = zynqmp_ipi_peek_data, + .last_tx_done = zynqmp_ipi_last_tx_done, + .send_data = zynqmp_ipi_send_data, +}; + +/** + * zynqmp_ipi_of_xlate - Translate of phandle to IPI mailbox channel + * + * @mbox: mailbox controller pointer + * @p: phandle pointer + * + * Return: Mailbox channel, else return error pointer. + */ +static struct mbox_chan *zynqmp_ipi_of_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *p) +{ + struct mbox_chan *chan; + struct device *dev = mbox->dev; + unsigned int chan_type; + + /* Only supports TX and RX channels */ + chan_type = p->args[0]; + if (chan_type != IPI_MB_CHNL_TX && chan_type != IPI_MB_CHNL_RX) { + dev_err(dev, "req chnl failure: invalid chnl type %u.\n", + chan_type); + return ERR_PTR(-EINVAL); + } + chan = &mbox->chans[chan_type]; + return chan; +} + +/** + * zynqmp_ipi_mbox_get_buf_res - Get buffer resource from the IPI dev node + * + * @node: IPI mbox device child node + * @name: name of the IPI buffer + * @res: pointer to where the resource information will be stored. + * + * Return: 0 for success, negative value for failure + */ +static int zynqmp_ipi_mbox_get_buf_res(struct device_node *node, + const char *name, + struct resource *res) +{ + int ret, index; + + index = of_property_match_string(node, "reg-names", name); + if (index >= 0) { + ret = of_address_to_resource(node, index, res); + if (ret < 0) + return -EINVAL; + return 0; + } + return -ENODEV; +} + +/** + * zynqmp_ipi_mbox_dev_release() - release the existence of a ipi mbox dev + * + * @dev: the ipi mailbox device + * + * This is to avoid the no device release() function kernel warning. + * + */ +static void zynqmp_ipi_mbox_dev_release(struct device *dev) +{ + (void)dev; +} + +/** + * zynqmp_ipi_mbox_probe - probe IPI mailbox resource from device node + * + * @ipi_mbox: pointer to IPI mailbox private data structure + * @node: IPI mailbox device node + * + * Return: 0 for success, negative value for failure + */ +static int zynqmp_ipi_mbox_probe(struct zynqmp_ipi_mbox *ipi_mbox, + struct device_node *node) +{ + struct mbox_chan *chans; + struct mbox_controller *mbox; + struct device *dev, *mdev; + int ret; + + dev = ipi_mbox->pdata->dev; + /* Initialize dev for IPI mailbox */ + ipi_mbox->dev.parent = dev; + ipi_mbox->dev.release = NULL; + ipi_mbox->dev.of_node = node; + dev_set_name(&ipi_mbox->dev, "%s", of_node_full_name(node)); + dev_set_drvdata(&ipi_mbox->dev, ipi_mbox); + ipi_mbox->dev.release = zynqmp_ipi_mbox_dev_release; + ipi_mbox->dev.driver = &zynqmp_ipi_mbox_driver; + ret = device_register(&ipi_mbox->dev); + if (ret) { + dev_err(dev, "Failed to register ipi mbox dev.\n"); + put_device(&ipi_mbox->dev); + return ret; + } + mdev = &ipi_mbox->dev; + + /* Get the IPI remote agent ID */ + ret = of_property_read_u32(node, "xlnx,ipi-id", &ipi_mbox->remote_id); + if (ret < 0) { + dev_err(dev, "No IPI remote ID is specified.\n"); + return ret; + } + + ret = ipi_mbox->setup_ipi_fn(ipi_mbox, node); + if (ret) { + dev_err(dev, "Failed to set up IPI Buffers.\n"); + return ret; + } + + mbox = &ipi_mbox->mbox; + mbox->dev = mdev; + mbox->ops = &zynqmp_ipi_chan_ops; + mbox->num_chans = 2; + mbox->txdone_irq = false; + mbox->txdone_poll = true; + mbox->txpoll_period = tx_poll_period; + mbox->of_xlate = zynqmp_ipi_of_xlate; + chans = devm_kzalloc(mdev, 2 * sizeof(*chans), GFP_KERNEL); + if (!chans) + return -ENOMEM; + mbox->chans = chans; + chans[IPI_MB_CHNL_TX].con_priv = &ipi_mbox->mchans[IPI_MB_CHNL_TX]; + chans[IPI_MB_CHNL_RX].con_priv = &ipi_mbox->mchans[IPI_MB_CHNL_RX]; + ipi_mbox->mchans[IPI_MB_CHNL_TX].chan_type = IPI_MB_CHNL_TX; + ipi_mbox->mchans[IPI_MB_CHNL_RX].chan_type = IPI_MB_CHNL_RX; + ret = devm_mbox_controller_register(mdev, mbox); + if (ret) + dev_err(mdev, + "Failed to register mbox_controller(%d)\n", ret); + else + dev_info(mdev, + "Registered ZynqMP IPI mbox with TX/RX channels.\n"); + return ret; +} + +/** + * zynqmp_ipi_setup - set up IPI Buffers for classic flow + * + * @ipi_mbox: pointer to IPI mailbox private data structure + * @node: IPI mailbox device node + * + * This will be used to set up IPI Buffers for ZynqMP SOC if user + * wishes to use classic driver usage model on new SOC's with only + * buffered IPIs. + * + * Note that bufferless IPIs and mixed usage of buffered and bufferless + * IPIs are not supported with this flow. + * + * This will be invoked with compatible string "xlnx,zynqmp-ipi-mailbox". + * + * Return: 0 for success, negative value for failure + */ +static int zynqmp_ipi_setup(struct zynqmp_ipi_mbox *ipi_mbox, + struct device_node *node) +{ + struct zynqmp_ipi_mchan *mchan; + struct device *mdev; + struct resource res; + const char *name; + int ret; + + mdev = &ipi_mbox->dev; + + mchan = &ipi_mbox->mchans[IPI_MB_CHNL_TX]; + name = "local_request_region"; + ret = zynqmp_ipi_mbox_get_buf_res(node, name, &res); + if (!ret) { + mchan->req_buf_size = resource_size(&res); + mchan->req_buf = devm_ioremap(mdev, res.start, + mchan->req_buf_size); + if (!mchan->req_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + } else if (ret != -ENODEV) { + dev_err(mdev, "Unmatched resource %s, %d.\n", name, ret); + return ret; + } + + name = "remote_response_region"; + ret = zynqmp_ipi_mbox_get_buf_res(node, name, &res); + if (!ret) { + mchan->resp_buf_size = resource_size(&res); + mchan->resp_buf = devm_ioremap(mdev, res.start, + mchan->resp_buf_size); + if (!mchan->resp_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + } else if (ret != -ENODEV) { + dev_err(mdev, "Unmatched resource %s.\n", name); + return ret; + } + mchan->rx_buf = devm_kzalloc(mdev, + mchan->resp_buf_size + + sizeof(struct zynqmp_ipi_message), + GFP_KERNEL); + if (!mchan->rx_buf) + return -ENOMEM; + + mchan = &ipi_mbox->mchans[IPI_MB_CHNL_RX]; + name = "remote_request_region"; + ret = zynqmp_ipi_mbox_get_buf_res(node, name, &res); + if (!ret) { + mchan->req_buf_size = resource_size(&res); + mchan->req_buf = devm_ioremap(mdev, res.start, + mchan->req_buf_size); + if (!mchan->req_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + } else if (ret != -ENODEV) { + dev_err(mdev, "Unmatched resource %s.\n", name); + return ret; + } + + name = "local_response_region"; + ret = zynqmp_ipi_mbox_get_buf_res(node, name, &res); + if (!ret) { + mchan->resp_buf_size = resource_size(&res); + mchan->resp_buf = devm_ioremap(mdev, res.start, + mchan->resp_buf_size); + if (!mchan->resp_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + } else if (ret != -ENODEV) { + dev_err(mdev, "Unmatched resource %s.\n", name); + return ret; + } + mchan->rx_buf = devm_kzalloc(mdev, + mchan->resp_buf_size + + sizeof(struct zynqmp_ipi_message), + GFP_KERNEL); + if (!mchan->rx_buf) + return -ENOMEM; + + return 0; +} + +/** + * versal_ipi_setup - Set up IPIs to support mixed usage of + * Buffered and Bufferless IPIs. + * + * @ipi_mbox: pointer to IPI mailbox private data structure + * @node: IPI mailbox device node + * + * Return: 0 for success, negative value for failure + */ +static int versal_ipi_setup(struct zynqmp_ipi_mbox *ipi_mbox, + struct device_node *node) +{ + struct zynqmp_ipi_mchan *tx_mchan, *rx_mchan; + struct resource host_res, remote_res; + struct device_node *parent_node; + int host_idx, remote_idx; + struct device *mdev; + + tx_mchan = &ipi_mbox->mchans[IPI_MB_CHNL_TX]; + rx_mchan = &ipi_mbox->mchans[IPI_MB_CHNL_RX]; + parent_node = of_get_parent(node); + mdev = &ipi_mbox->dev; + + host_idx = zynqmp_ipi_mbox_get_buf_res(parent_node, "msg", &host_res); + remote_idx = zynqmp_ipi_mbox_get_buf_res(node, "msg", &remote_res); + + /* + * Only set up buffers if both sides claim to have msg buffers. + * This is because each buffered IPI's corresponding msg buffers + * are reserved for use by other buffered IPI's. + */ + if (!host_idx && !remote_idx) { + u32 host_src, host_dst, remote_src, remote_dst; + u32 buff_sz; + + buff_sz = resource_size(&host_res); + + host_src = host_res.start & SRC_BITMASK; + remote_src = remote_res.start & SRC_BITMASK; + + host_dst = (host_src >> DST_BIT_POS) * DEST_OFFSET; + remote_dst = (remote_src >> DST_BIT_POS) * DEST_OFFSET; + + /* Validate that IPI IDs is within IPI Message buffer space. */ + if (host_dst >= buff_sz || remote_dst >= buff_sz) { + dev_err(mdev, + "Invalid IPI Message buffer values: %x %x\n", + host_dst, remote_dst); + return -EINVAL; + } + + tx_mchan->req_buf = devm_ioremap(mdev, + host_res.start | remote_dst, + IPI_BUF_SIZE); + if (!tx_mchan->req_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + + tx_mchan->resp_buf = devm_ioremap(mdev, + (remote_res.start | host_dst) + + RESP_OFFSET, IPI_BUF_SIZE); + if (!tx_mchan->resp_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + + rx_mchan->req_buf = devm_ioremap(mdev, + remote_res.start | host_dst, + IPI_BUF_SIZE); + if (!rx_mchan->req_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + + rx_mchan->resp_buf = devm_ioremap(mdev, + (host_res.start | remote_dst) + + RESP_OFFSET, IPI_BUF_SIZE); + if (!rx_mchan->resp_buf) { + dev_err(mdev, "Unable to map IPI buffer I/O memory\n"); + return -ENOMEM; + } + + tx_mchan->resp_buf_size = IPI_BUF_SIZE; + tx_mchan->req_buf_size = IPI_BUF_SIZE; + tx_mchan->rx_buf = devm_kzalloc(mdev, IPI_BUF_SIZE + + sizeof(struct zynqmp_ipi_message), + GFP_KERNEL); + if (!tx_mchan->rx_buf) + return -ENOMEM; + + rx_mchan->resp_buf_size = IPI_BUF_SIZE; + rx_mchan->req_buf_size = IPI_BUF_SIZE; + rx_mchan->rx_buf = devm_kzalloc(mdev, IPI_BUF_SIZE + + sizeof(struct zynqmp_ipi_message), + GFP_KERNEL); + if (!rx_mchan->rx_buf) + return -ENOMEM; + } + + return 0; +} + +static int xlnx_mbox_cpuhp_start(unsigned int cpu) +{ + struct zynqmp_ipi_pdata *pdata; + + pdata = get_cpu_var(per_cpu_pdata); + put_cpu_var(per_cpu_pdata); + enable_percpu_irq(pdata->virq_sgi, IRQ_TYPE_NONE); + + return 0; +} + +static int xlnx_mbox_cpuhp_down(unsigned int cpu) +{ + struct zynqmp_ipi_pdata *pdata; + + pdata = get_cpu_var(per_cpu_pdata); + put_cpu_var(per_cpu_pdata); + disable_percpu_irq(pdata->virq_sgi); + + return 0; +} + +static void xlnx_disable_percpu_irq(void *data) +{ + struct zynqmp_ipi_pdata *pdata; + + pdata = *this_cpu_ptr(&per_cpu_pdata); + + disable_percpu_irq(pdata->virq_sgi); +} + +static int xlnx_mbox_init_sgi(struct platform_device *pdev, + int sgi_num, + struct zynqmp_ipi_pdata *pdata) +{ + int ret = 0; + int cpu; + + /* + * IRQ related structures are used for the following: + * for each SGI interrupt ensure its mapped by GIC IRQ domain + * and that each corresponding linux IRQ for the HW IRQ has + * a handler for when receiving an interrupt from the remote + * processor. + */ + struct irq_domain *domain; + struct irq_fwspec sgi_fwspec; + struct device_node *interrupt_parent = NULL; + struct device *dev = &pdev->dev; + + /* Find GIC controller to map SGIs. */ + interrupt_parent = of_irq_find_parent(dev->of_node); + if (!interrupt_parent) { + dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n"); + return -EINVAL; + } + + /* Each SGI needs to be associated with GIC's IRQ domain. */ + domain = irq_find_host(interrupt_parent); + of_node_put(interrupt_parent); + + /* Each mapping needs GIC domain when finding IRQ mapping. */ + sgi_fwspec.fwnode = domain->fwnode; + + /* + * When irq domain looks at mapping each arg is as follows: + * 3 args for: interrupt type (SGI), interrupt # (set later), type + */ + sgi_fwspec.param_count = 1; + + /* Set SGI's hwirq */ + sgi_fwspec.param[0] = sgi_num; + pdata->virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec); + + for_each_possible_cpu(cpu) + per_cpu(per_cpu_pdata, cpu) = pdata; + + ret = request_percpu_irq(pdata->virq_sgi, zynqmp_sgi_interrupt, pdev->name, + &per_cpu_pdata); + WARN_ON(ret); + if (ret) { + irq_dispose_mapping(pdata->virq_sgi); + return ret; + } + + irq_set_status_flags(pdata->virq_sgi, IRQ_PER_CPU); + + /* Setup function for the CPU hot-plug cases */ + cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "mailbox/sgi:starting", + xlnx_mbox_cpuhp_start, xlnx_mbox_cpuhp_down); + + return ret; +} + +static void xlnx_mbox_cleanup_sgi(struct zynqmp_ipi_pdata *pdata) +{ + cpuhp_remove_state(CPUHP_AP_ONLINE_DYN); + + on_each_cpu(xlnx_disable_percpu_irq, NULL, 1); + + irq_clear_status_flags(pdata->virq_sgi, IRQ_PER_CPU); + free_percpu_irq(pdata->virq_sgi, &per_cpu_pdata); + irq_dispose_mapping(pdata->virq_sgi); +} + +/** + * zynqmp_ipi_free_mboxes - Free IPI mailboxes devices + * + * @pdata: IPI private data + */ +static void zynqmp_ipi_free_mboxes(struct zynqmp_ipi_pdata *pdata) +{ + struct zynqmp_ipi_mbox *ipi_mbox; + int i; + + if (pdata->irq_type == IPI_IRQ_TYPE_SGI) + xlnx_mbox_cleanup_sgi(pdata); + + i = pdata->num_mboxes - 1; + for (; i >= 0; i--) { + ipi_mbox = &pdata->ipi_mboxes[i]; + if (device_is_registered(&ipi_mbox->dev)) + device_unregister(&ipi_mbox->dev); + } +} + +static int zynqmp_ipi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct zynqmp_ipi_pdata *pdata; + struct of_phandle_args out_irq; + struct zynqmp_ipi_mbox *mbox; + int num_mboxes, ret = -EINVAL; + setup_ipi_fn ipi_fn; + + num_mboxes = of_get_available_child_count(np); + if (num_mboxes == 0) { + dev_err(dev, "mailbox nodes not available\n"); + return -EINVAL; + } + + pdata = devm_kzalloc(dev, struct_size(pdata, ipi_mboxes, num_mboxes), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + pdata->dev = dev; + + /* Get the IPI local agents ID */ + ret = of_property_read_u32(np, "xlnx,ipi-id", &pdata->local_id); + if (ret < 0) { + dev_err(dev, "No IPI local ID is specified.\n"); + return ret; + } + + ipi_fn = (setup_ipi_fn)device_get_match_data(&pdev->dev); + if (!ipi_fn) { + dev_err(dev, + "Mbox Compatible String is missing IPI Setup fn.\n"); + return -ENODEV; + } + + pdata->num_mboxes = num_mboxes; + + mbox = pdata->ipi_mboxes; + for_each_available_child_of_node_scoped(np, nc) { + mbox->pdata = pdata; + mbox->setup_ipi_fn = ipi_fn; + + ret = zynqmp_ipi_mbox_probe(mbox, nc); + if (ret) { + dev_err(dev, "failed to probe subdev.\n"); + ret = -EINVAL; + goto free_mbox_dev; + } + mbox++; + } + + ret = of_irq_parse_one(dev_of_node(dev), 0, &out_irq); + if (ret < 0) { + dev_err(dev, "failed to parse interrupts\n"); + goto free_mbox_dev; + } + + /* Use interrupt type to distinguish SGI and SPI interrupts */ + pdata->irq_type = out_irq.args[0]; + + /* + * If Interrupt number is in SGI range, then request SGI else request + * IPI system IRQ. + */ + if (pdata->irq_type == IPI_IRQ_TYPE_SGI) { + pdata->irq = out_irq.args[1]; + ret = xlnx_mbox_init_sgi(pdev, pdata->irq, pdata); + if (ret) + goto free_mbox_dev; + } else { + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto free_mbox_dev; + + pdata->irq = ret; + ret = devm_request_irq(dev, pdata->irq, zynqmp_ipi_interrupt, + IRQF_SHARED, dev_name(dev), pdata); + } + + if (ret) { + dev_err(dev, "IRQ %d is not requested successfully.\n", + pdata->irq); + goto free_mbox_dev; + } + + platform_set_drvdata(pdev, pdata); + return ret; + +free_mbox_dev: + zynqmp_ipi_free_mboxes(pdata); + return ret; +} + +static void zynqmp_ipi_remove(struct platform_device *pdev) +{ + struct zynqmp_ipi_pdata *pdata; + + pdata = platform_get_drvdata(pdev); + zynqmp_ipi_free_mboxes(pdata); +} + +static const struct of_device_id zynqmp_ipi_of_match[] = { + { .compatible = "xlnx,zynqmp-ipi-mailbox", + .data = &zynqmp_ipi_setup, + }, + { .compatible = "xlnx,versal-ipi-mailbox", + .data = &versal_ipi_setup, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, zynqmp_ipi_of_match); + +static struct platform_driver zynqmp_ipi_driver = { + .probe = zynqmp_ipi_probe, + .remove = zynqmp_ipi_remove, + .driver = { + .name = "zynqmp-ipi", + .of_match_table = of_match_ptr(zynqmp_ipi_of_match), + }, +}; + +static int __init zynqmp_ipi_init(void) +{ + return platform_driver_register(&zynqmp_ipi_driver); +} +subsys_initcall(zynqmp_ipi_init); + +static void __exit zynqmp_ipi_exit(void) +{ + platform_driver_unregister(&zynqmp_ipi_driver); +} +module_exit(zynqmp_ipi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Xilinx ZynqMP IPI Mailbox driver"); +MODULE_AUTHOR("Xilinx Inc."); |
