284 lines
6.7 KiB
C
284 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2021 MediaTek Inc.
|
|
*/
|
|
#define MBOX_TIMESTAMP
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/of_device.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>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_fdt.h>
|
|
#ifdef CONFIG_OF_RESERVED_MEM
|
|
#include <linux/of_reserved_mem.h>
|
|
#endif
|
|
#ifdef CONFIG_MTK_SCMI_TIMEOUT_HOOK
|
|
#include <trace/hooks/scmi.h>
|
|
#endif
|
|
|
|
#ifdef MBOX_TIMESTAMP
|
|
#include <asm/arch_timer.h>
|
|
#define FIXED_MBOX_SIZE 128
|
|
#define T_SEND_OFFSET_H (FIXED_MBOX_SIZE - 16)
|
|
#define T_SEND_OFFSET_L (FIXED_MBOX_SIZE - 12)
|
|
#define T_IRQ_OFFSET_H (FIXED_MBOX_SIZE - 8)
|
|
#define T_IRQ_OFFSET_L (FIXED_MBOX_SIZE - 4)
|
|
#endif
|
|
#define INTR_SET_OFS 0x0
|
|
#define INTR_CLR_OFS 0x4
|
|
|
|
#define MBOX_CHANS 2
|
|
#define MBOX_DEBUG 0
|
|
|
|
|
|
struct mhu_link {
|
|
unsigned irq;
|
|
void __iomem *tx_reg;
|
|
void __iomem *rx_reg;
|
|
#ifdef MBOX_TIMESTAMP
|
|
void __iomem *shmem;
|
|
resource_size_t shmem_size;
|
|
#endif
|
|
};
|
|
|
|
struct mtk_mbu {
|
|
void __iomem *base;
|
|
struct mhu_link mlink[MBOX_CHANS];
|
|
struct mbox_chan chan[MBOX_CHANS];
|
|
struct mbox_controller mbox;
|
|
};
|
|
|
|
static irqreturn_t tinysys_mbox_rx_interrupt(int irq, void *p)
|
|
{
|
|
struct mbox_chan *chan = p;
|
|
struct mhu_link *mlink = chan->con_priv;
|
|
u32 val;
|
|
#ifdef MBOX_TIMESTAMP
|
|
u64 tv;
|
|
|
|
tv = __arch_counter_get_cntvct();
|
|
writel_relaxed((u32)((tv & 0xFFFFFFFF00000000LL) >> 32), mlink->shmem + T_IRQ_OFFSET_H);
|
|
writel_relaxed((u32)(tv & 0xFFFFFFFFLL), mlink->shmem + T_IRQ_OFFSET_L);
|
|
#endif
|
|
|
|
val = readl_relaxed(mlink->rx_reg + INTR_CLR_OFS);
|
|
#if MBOX_DEBUG
|
|
dev_notice(chan->mbox->dev,
|
|
"[scmi] tinysys_mbox_rx_interrupt chan:%px txdone_method:%x\n",
|
|
chan, chan->txdone_method);
|
|
#endif
|
|
if (!val)
|
|
return IRQ_NONE;
|
|
|
|
writel_relaxed(1, mlink->rx_reg + INTR_CLR_OFS);
|
|
mbox_chan_received_data(chan, (void *)&val);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static bool tinysys_mbox_last_tx_done(struct mbox_chan *chan)
|
|
{
|
|
struct mhu_link *mlink = chan->con_priv;
|
|
u32 val = readl_relaxed(mlink->tx_reg + INTR_SET_OFS);
|
|
|
|
#if MBOX_DEBUG
|
|
if (val == 0)
|
|
dev_notice(chan->mbox->dev, "[scmi] last_tx_done\n");
|
|
#endif
|
|
return (val == 0);
|
|
}
|
|
|
|
static int tinysys_mbox_send_data(struct mbox_chan *chan, void *data)
|
|
{
|
|
struct mhu_link *mlink = chan->con_priv;
|
|
#ifdef MBOX_TIMESTAMP
|
|
u64 tv;
|
|
#endif
|
|
#if MBOX_DEBUG
|
|
u32 *arg = data;
|
|
|
|
dev_notice(chan->mbox->dev, "[scmi] send_data %x\n", *arg);
|
|
#endif
|
|
smp_mb();
|
|
#ifdef MBOX_TIMESTAMP
|
|
tv = __arch_counter_get_cntvct();
|
|
writel_relaxed((u32)((tv & 0xFFFFFFFF00000000LL) >> 32), mlink->shmem + T_SEND_OFFSET_H);
|
|
writel_relaxed((u32)(tv & 0xFFFFFFFFLL), mlink->shmem + T_SEND_OFFSET_L);
|
|
#endif
|
|
writel_relaxed(1, mlink->tx_reg + INTR_SET_OFS);
|
|
|
|
return 0;
|
|
}
|
|
#ifdef CONFIG_MTK_SCMI_TIMEOUT_HOOK
|
|
static void scmi_timeout_set(void *ignore, int *timeout)
|
|
{
|
|
int rx_timeout = msecs_to_jiffies(2000);
|
|
*timeout = rx_timeout;
|
|
}
|
|
#endif
|
|
static int tinysys_mbox_startup(struct mbox_chan *chan)
|
|
{
|
|
struct mhu_link *mlink = chan->con_priv;
|
|
int ret;
|
|
|
|
ret = request_irq(mlink->irq, tinysys_mbox_rx_interrupt,
|
|
IRQF_NO_SUSPEND | IRQF_TRIGGER_NONE, "mtk_tinysys_mbox", chan);
|
|
if (ret) {
|
|
dev_notice(chan->mbox->dev,
|
|
"Unable to acquire IRQ %d\n", mlink->irq);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void tinysys_mbox_shutdown(struct mbox_chan *chan)
|
|
{
|
|
struct mhu_link *mlink = chan->con_priv;
|
|
|
|
free_irq(mlink->irq, chan);
|
|
}
|
|
|
|
static const struct mbox_chan_ops tinysys_mbox_chan_ops = {
|
|
.send_data = tinysys_mbox_send_data,
|
|
.startup = tinysys_mbox_startup,
|
|
.shutdown = tinysys_mbox_shutdown,
|
|
.last_tx_done = tinysys_mbox_last_tx_done,
|
|
};
|
|
|
|
static int tinysys_mbox_probe(struct platform_device *pdev)
|
|
{
|
|
int err, i;
|
|
struct mtk_mbu *mbu;
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
struct tinysys_mbox_plat *plat_data;
|
|
#ifdef MBOX_TIMESTAMP
|
|
struct device_node *shmem;
|
|
struct resource shmem_res;
|
|
resource_size_t size;
|
|
#endif
|
|
#ifdef CONFIG_MTK_SCMI_TIMEOUT_HOOK
|
|
int ret;
|
|
#endif
|
|
|
|
/* Allocate memory for device */
|
|
mbu = devm_kzalloc(dev, sizeof(*mbu), GFP_KERNEL);
|
|
if (!mbu)
|
|
return -ENOMEM;
|
|
#ifdef CONFIG_MTK_SCMI_TIMEOUT_HOOK
|
|
/* register tracepoint of scmi mailbox rx timeout */
|
|
ret = register_trace_android_vh_scmi_timeout_sync(scmi_timeout_set, NULL);
|
|
if (ret) {
|
|
dev_notice(dev, "scmi register hooks fail");
|
|
return ret;
|
|
}
|
|
#endif
|
|
for (i = 0; i < MBOX_CHANS; i++) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
|
|
mbu->base = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(mbu->base)) {
|
|
dev_notice(dev, "failed to ioremap mbu");
|
|
return PTR_ERR(mbu->base);
|
|
}
|
|
#ifdef MBOX_TIMESTAMP
|
|
shmem = of_parse_phandle(dev->of_node, "shmem", i);
|
|
err = of_address_to_resource(shmem, 0, &shmem_res);
|
|
of_node_put(shmem);
|
|
if (err) {
|
|
dev_err(dev, "failed to get scmi profile memory\n");
|
|
return err;
|
|
}
|
|
size = resource_size(&shmem_res);
|
|
mbu->mlink[i].shmem = devm_ioremap(dev, shmem_res.start, size);
|
|
mbu->mlink[i].shmem_size = size;
|
|
#endif
|
|
mbu->chan[i].con_priv = &mbu->mlink[i];
|
|
|
|
mbu->mlink[i].irq = platform_get_irq(pdev, i);
|
|
if (!mbu->mlink[i].irq)
|
|
dev_notice(dev, "failed to get irq");
|
|
|
|
mbu->mlink[i].rx_reg = mbu->base;
|
|
mbu->mlink[i].tx_reg = mbu->base;
|
|
}
|
|
|
|
plat_data = (struct tinysys_mbox_plat *)of_device_get_match_data(dev);
|
|
if (!plat_data) {
|
|
dev_notice(dev, "failed to get match data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mbu->mbox.dev = dev;
|
|
mbu->mbox.chans = &mbu->chan[0];
|
|
mbu->mbox.num_chans = MBOX_CHANS;
|
|
mbu->mbox.ops = &tinysys_mbox_chan_ops;
|
|
mbu->mbox.txdone_irq = false;
|
|
mbu->mbox.txdone_poll = true;
|
|
mbu->mbox.txpoll_period = 1;
|
|
|
|
platform_set_drvdata(pdev, mbu);
|
|
|
|
err = devm_mbox_controller_register(dev, &mbu->mbox);
|
|
if (err) {
|
|
dev_notice(dev, "Failed to register mailboxes %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
dev_info(dev, "MTK MBOX Mailbox registered\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
struct tinysys_mbox_plat {
|
|
u32 thread_nr;
|
|
u32 bfs;
|
|
};
|
|
|
|
static const struct tinysys_mbox_plat tinysys_mbox_plat_v1 = {.thread_nr = 1, .bfs = 64};
|
|
|
|
static const struct of_device_id tinysys_mbox_of_ids[] = {
|
|
{.compatible = "mediatek,tinysys_mbox", .data = (void *)&tinysys_mbox_plat_v1},
|
|
{}
|
|
};
|
|
|
|
static int tinysys_mbox_remove(struct platform_device *pdev)
|
|
{
|
|
#ifdef CONFIG_MTK_SCMI_TIMEOUT_HOOK
|
|
unregister_trace_android_vh_scmi_timeout_sync(scmi_timeout_set, NULL);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver tinysys_mbox_drv = {
|
|
.probe = tinysys_mbox_probe,
|
|
.remove = tinysys_mbox_remove,
|
|
.driver = {
|
|
.name = "mtk_tinysys_mbox",
|
|
.of_match_table = tinysys_mbox_of_ids,
|
|
}
|
|
};
|
|
static __init int mtk_tinysys_mbox_driver(void)
|
|
{
|
|
u32 err = 0;
|
|
|
|
err = platform_driver_register(&tinysys_mbox_drv);
|
|
if (err) {
|
|
pr_notice("mtk_tinysys_mbox_driver failed:%d", err);
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
module_init(mtk_tinysys_mbox_driver);
|
|
MODULE_LICENSE("GPL v2");
|
|
|