Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+402
View File
@@ -0,0 +1,402 @@
# 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.
endif
+86
View File
@@ -0,0 +1,86 @@
# 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
+176
View File
@@ -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>");
+351
View File
@@ -0,0 +1,351 @@
// 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) {
dev_err(dev, "Can't claim IRQ %d\n", irq);
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>");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
// 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) {
dev_err(mbox->dev, "Cannot request irq\n");
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>");
@@ -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");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,200 @@
// 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;
int ret = 0;
struct bcm2835_mbox *mbox;
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
if (mbox == NULL)
return -ENOMEM;
spin_lock_init(&mbox->lock);
ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0),
bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev),
mbox);
if (ret) {
dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
ret);
return -ENODEV;
}
mbox->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mbox->regs)) {
ret = PTR_ERR(mbox->regs);
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;
ret = devm_mbox_controller_register(dev, &mbox->controller);
if (ret)
return ret;
platform_set_drvdata(pdev, mbox);
dev_info(dev, "mailbox enabled\n");
return ret;
}
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");
@@ -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 dev_err_probe(dev, ret, "Failed to request irq\n");
mbox->controller.ops = &bcm74110_mbox_chan_ops;
mbox->controller.dev = dev;
mbox->controller.num_chans = BCM_MSG_SVC_MAX;
mbox->controller.of_xlate = &bcm74110_mbox_of_xlate;
mbox->controller.chans = devm_kcalloc(dev, BCM_MSG_SVC_MAX,
sizeof(*mbox->controller.chans),
GFP_KERNEL);
if (!mbox->controller.chans)
return -ENOMEM;
mbox->mbox_chan = devm_kcalloc(dev, BCM_MSG_SVC_MAX,
sizeof(*mbox->mbox_chan),
GFP_KERNEL);
if (!mbox->mbox_chan)
return -ENOMEM;
for (i = 0; i < BCM_MSG_SVC_MAX; i++) {
mbox->mbox_chan[i].mbox = mbox;
mbox->controller.chans[i].con_priv = &mbox->mbox_chan[i];
}
ret = devm_mbox_controller_register(dev, &mbox->controller);
if (ret)
return ret;
ret = bcm74110_mbox_init(mbox);
if (ret)
return ret;
return 0;
}
static const struct of_device_id bcm74110_mbox_of_match[] = {
{ .compatible = "brcm,bcm74110-mbox", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, bcm74110_mbox_of_match);
static struct platform_driver bcm74110_mbox_driver = {
.driver = {
.name = "bcm74110-mbox",
.of_match_table = bcm74110_mbox_of_match,
},
.probe = bcm74110_mbox_probe,
.shutdown = bcm74110_mbox_shutdown,
};
module_platform_driver(bcm74110_mbox_driver);
MODULE_AUTHOR("Justin Chen <justin.chen@broadcom.com>");
MODULE_DESCRIPTION("BCM74110 mailbox driver");
MODULE_LICENSE("GPL");
+643
View File
@@ -0,0 +1,643 @@
// 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)) {
dev_err(priv->dev, "cix,mbox_dir property not found\n");
return -EINVAL;
}
if (!strcmp(dir_str, "tx"))
priv->dir = 0;
else if (!strcmp(dir_str, "rx"))
priv->dir = 1;
else {
dev_err(priv->dev, "cix,mbox_dir=%s is not expected\n", dir_str);
return -EINVAL;
}
cix_mbox_init(priv);
priv->mbox.dev = dev;
priv->mbox.ops = &cix_mbox_chan_ops;
priv->mbox.chans = priv->mbox_chans;
priv->mbox.txdone_irq = true;
priv->mbox.num_chans = CIX_MBOX_CHANS;
priv->mbox.of_xlate = NULL;
platform_set_drvdata(pdev, priv);
ret = devm_mbox_controller_register(dev, &priv->mbox);
if (ret)
dev_err(dev, "Failed to register mailbox %d\n", ret);
return ret;
}
static const struct of_device_id cix_mbox_dt_ids[] = {
{ .compatible = "cix,sky1-mbox" },
{ },
};
MODULE_DEVICE_TABLE(of, cix_mbox_dt_ids);
static struct platform_driver cix_mbox_driver = {
.probe = cix_mbox_probe,
.driver = {
.name = "cix_mbox",
.of_match_table = cix_mbox_dt_ids,
},
};
static int __init cix_mailbox_init(void)
{
return platform_driver_register(&cix_mbox_driver);
}
arch_initcall(cix_mailbox_init);
MODULE_AUTHOR("Cix Technology Group Co., Ltd.");
MODULE_DESCRIPTION("CIX mailbox driver");
MODULE_LICENSE("GPL");
@@ -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 dev_err_probe(dev, err, "Failed to register irq\n");
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");
@@ -0,0 +1,145 @@
// 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)
/**
* struct exynos_mbox - driver's private data.
* @regs: mailbox registers base address.
* @mbox: pointer to the mailbox controller.
*/
struct exynos_mbox {
void __iomem *regs;
struct mbox_controller *mbox;
};
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;
}
writel(BIT(msg->chan_id), exynos_mbox->regs + EXYNOS_MBOX_INTGR1);
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" },
{},
};
MODULE_DEVICE_TABLE(of, exynos_mbox_match);
static int exynos_mbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct exynos_mbox *exynos_mbox;
struct mbox_controller *mbox;
struct mbox_chan *chans;
struct clk *pclk;
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, EXYNOS_MBOX_CHAN_COUNT, 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");
mbox->num_chans = EXYNOS_MBOX_CHAN_COUNT;
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(EXYNOS_MBOX_INTMR0_MASK, exynos_mbox->regs + EXYNOS_MBOX_INTMR0);
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");
@@ -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>");
@@ -0,0 +1,367 @@
// 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) {
dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
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");
File diff suppressed because it is too large Load Diff
@@ -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");
@@ -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");
+339
View File
@@ -0,0 +1,339 @@
// 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);
if (ret)
dev_err(mbox->dev, "failed to register mailbox interrupt:%d\n", ret);
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");
+493
View File
@@ -0,0 +1,493 @@
// 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) {
dev_err(&pdev->dev, "Can't claim IRQ %d\n", irq);
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");
+463
View File
@@ -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");
@@ -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");
@@ -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");
+711
View File
@@ -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 */
@@ -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");
@@ -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");
@@ -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");
@@ -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");
+617
View File
@@ -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");
+897
View File
@@ -0,0 +1,897 @@
// 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: this flag is used just to check if the interrupt needs
* handling when it is shared. Since only one transfer can occur
* at a time and mailbox takes care of locking, this flag can be
* accessed without a lock. Note: the type only support the
* communication from OSPM to Platform, like type3, use it, and
* other types completely ignore 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;
if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE &&
!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 interrupt ack register and just
* before mbox_chan_received_data() which might call pcc_send_data()
* where the flag is set again to start new transfer. This is
* required to avoid any possible race in updatation of this flag.
*/
pchan->chan_in_use = false;
mbox_chan_received_data(chan, NULL);
mbox_chan_txdone(chan, 0);
pcc_chan_acknowledge(pchan);
return IRQ_HANDLED;
}
/**
* 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 = mbox_bind_client(chan, cl);
if (rc) {
iounmap(pcc_mchan->shmem);
pcc_mchan->shmem = NULL;
return ERR_PTR(rc);
}
return pcc_mchan;
}
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;
ret = pcc_chan_reg_read_modify_write(&pchan->db);
if (!ret && pchan->plat_irq > 0)
pchan->chan_in_use = true;
return ret;
}
static bool pcc_last_tx_done(struct mbox_chan *chan)
{
struct pcc_chan_info *pchan = chan->con_priv;
return pcc_mbox_cmd_complete_check(pchan);
}
/**
* 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);
+175
View File
@@ -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);
+183
View File
@@ -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>");
@@ -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");
@@ -0,0 +1,212 @@
// 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];
unsigned long flags;
/* Provide mutual exclusion with changes to chan->cl */
spin_lock_irqsave(&chan->lock, flags);
if (chan->cl)
mbox_chan_received_data(chan, &val);
writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR);
spin_unlock_irqrestore(&chan->lock, flags);
}
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;
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 dev_err_probe(dev, ret, "Failed to register irq: %d\n", irq);
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");
+382
View File
@@ -0,0 +1,382 @@
// 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;
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)
break;
else if (mchan->client_id == ph->args[0] &&
mchan->signal_id == ph->args[1])
return ERR_PTR(-EBUSY);
}
if (chan_id >= mbox->num_chans)
return ERR_PTR(-EBUSY);
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) {
dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret);
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");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,250 @@
// 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;
struct clk *pclk;
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;
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);
mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox");
if (IS_ERR(mb->pclk)) {
ret = PTR_ERR(mb->pclk);
dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n",
ret);
return ret;
}
ret = clk_prepare_enable(mb->pclk);
if (ret) {
dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret);
return ret;
}
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>");
+472
View File
@@ -0,0 +1,472 @@
// 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) {
dev_err(dev, "failed to request inbox IRQ: %d\n", ret);
return ret;
}
outbox_irq = platform_get_irq_byname(pdev, "outbox");
if (outbox_irq < 0)
return outbox_irq;
ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr,
IRQF_NO_SUSPEND, dev_name(dev), priv);
if (ret) {
dev_err(dev, "failed to request outbox IRQ: %d\n", ret);
return ret;
}
/* Supplementary outbox IRQ is optional */
supp_irq = platform_get_irq_byname(pdev, "supp-outbox");
if (supp_irq > 0) {
ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr,
IRQF_NO_SUSPEND, dev_name(dev), priv);
if (ret) {
dev_err(dev, "failed to request outbox IRQ: %d\n", ret);
return ret;
}
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");
+390
View File
@@ -0,0 +1,390 @@
// 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) {
dev_err(dev, "failed to request irq %lu (%d)\n", i, 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");
+317
View File
@@ -0,0 +1,317 @@
// 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) {
dev_err(dev, "Failed to register IRQ handler: %d\n", 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");
File diff suppressed because it is too large Load Diff
+932
View File
@@ -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");
File diff suppressed because it is too large Load Diff