Files
chromebox_10th_audio_driver/chromiumos_kernel/v5.15/drivers/mailbox/mtk-apu-mailbox.c

219 lines
5.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 MediaTek Inc.
*/
#include <asm/io.h>
#include <linux/bits.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/mailbox/mtk-apu-mailbox.h>
#include <linux/platform_device.h>
#define INBOX (0x0)
#define OUTBOX (0x20)
#define INBOX_IRQ (0xc0)
#define OUTBOX_IRQ (0xc4)
#define INBOX_IRQ_MASK (0xd0)
#define SPARE_OFF_START (0x40)
#define SPARE_OFF_END (0xB0)
#define MSG_MBOX_SLOTS (8)
struct mtk_apu_mailbox {
struct device *dev;
void __iomem *regs;
struct mbox_controller controller;
u32 msgs[MSG_MBOX_SLOTS];
};
struct mtk_apu_mailbox *g_mbox;
static irqreturn_t mtk_apu_mailbox_irq_top_half(int irq, void *dev_id)
{
struct mtk_apu_mailbox *mbox = dev_id;
struct mbox_chan *link = &mbox->controller.chans[0];
int i;
for (i = 0; i < MSG_MBOX_SLOTS; i++)
mbox->msgs[i] = readl(mbox->regs + OUTBOX + i * sizeof(u32));
mbox_chan_received_data(link, &mbox->msgs);
return IRQ_WAKE_THREAD;
}
static irqreturn_t mtk_apu_mailbox_irq_btm_half(int irq, void *dev_id)
{
struct mtk_apu_mailbox *mbox = dev_id;
struct mbox_chan *link = &mbox->controller.chans[0];
mbox_chan_received_data_bh(link, NULL);
writel(readl(mbox->regs + OUTBOX_IRQ), mbox->regs + OUTBOX_IRQ);
return IRQ_HANDLED;
}
static int mtk_apu_mailbox_send_data(struct mbox_chan *chan, void *data)
{
struct mtk_apu_mailbox *mbox = container_of(chan->mbox,
struct mtk_apu_mailbox,
controller);
int send_cnt = ((int*)data)[0];
int i;
if (send_cnt <= 0 || send_cnt > MSG_MBOX_SLOTS) {
dev_err(mbox->dev, "%s: invalid send_cnt %d\n", __func__, send_cnt);
return -EINVAL;
}
/*
* Mask lowest "send_cnt-1" interrupts bits, so the interrupt on the other side
* triggers only after the last data slot is written (sent).
*/
writel(GENMASK(send_cnt-2, 0), mbox->regs + INBOX_IRQ_MASK);
for (i = 0; i < send_cnt; i++)
writel(((u32 *)data)[i+1], mbox->regs + INBOX + i * sizeof(u32));
return 0;
}
static bool mtk_apu_mailbox_last_tx_done(struct mbox_chan *chan)
{
struct mtk_apu_mailbox *mbox = container_of(chan->mbox,
struct mtk_apu_mailbox,
controller);
return readl(mbox->regs + INBOX_IRQ) == 0;
}
static const struct mbox_chan_ops mtk_apu_mailbox_ops = {
.send_data = mtk_apu_mailbox_send_data,
.last_tx_done = mtk_apu_mailbox_last_tx_done,
};
/**
* mtk_apu_mbox_write - write value to specifice mtk_apu_mbox spare register.
* @val: value to be written.
* @offset: offset of the spare register.
*/
void mtk_apu_mbox_write(u32 val, u32 offset)
{
if (!g_mbox) {
pr_err("mtk apu mbox was not initialized, stop writing register\n");
return;
}
if (offset < SPARE_OFF_START || offset >= SPARE_OFF_END) {
dev_err(g_mbox->dev, "Invalid offset %d for mtk apu mbox spare register\n", offset);
return;
}
writel(val, g_mbox->regs + offset);
}
EXPORT_SYMBOL_NS(mtk_apu_mbox_write, MTK_APU_MAILBOX);
/**
* mtk_apu_mbox_read - read value to specifice mtk_apu_mbox spare register.
* @offset: offset of the spare register.
*/
u32 mtk_apu_mbox_read(u32 offset)
{
if (!g_mbox) {
pr_err("mtk apu mbox was not initialized, stop reading register\n");
return 0;
}
if (offset < SPARE_OFF_START || offset >= SPARE_OFF_END) {
dev_err(g_mbox->dev, "Invalid offset %d for mtk apu mbox spare register\n", offset);
return 0;
}
return readl(g_mbox->regs + offset);
}
EXPORT_SYMBOL_NS(mtk_apu_mbox_read, MTK_APU_MAILBOX);
static int mtk_apu_mailbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mtk_apu_mailbox *mbox;
int irq = -1, ret = 0;
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
if (!mbox)
return -ENOMEM;
mbox->dev = dev;
platform_set_drvdata(pdev, mbox);
mbox->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mbox->regs))
return PTR_ERR(mbox->regs);
mbox->controller.txdone_irq = false;
mbox->controller.txdone_poll = true;
mbox->controller.txpoll_period = 1;
mbox->controller.ops = &mtk_apu_mailbox_ops;
mbox->controller.dev = dev;
/*
* Here we only register 1 mbox channel
* The remaining channels are used by other modules
*/
mbox->controller.num_chans = 1;
mbox->controller.chans = devm_kcalloc(dev, mbox->controller.num_chans,
sizeof(*mbox->controller.chans),
GFP_KERNEL);
if (!mbox->controller.chans)
return -ENOMEM;
ret = devm_mbox_controller_register(dev, &mbox->controller);
if (ret)
return ret;
dev_info(dev, "registered mtk apu mailbox\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "Failed to get mtk apu mailbox IRQ\n");
return irq;
}
ret = devm_request_threaded_irq(dev, irq, mtk_apu_mailbox_irq_top_half,
mtk_apu_mailbox_irq_btm_half, IRQF_ONESHOT,
dev_name(dev), mbox);
if (ret) {
dev_err(dev, "Failed to register mtk apu mailbox IRQ: %d\n", ret);
return -ENODEV;
}
g_mbox = mbox;
return 0;
}
static int mtk_apu_mailbox_remove(struct platform_device *pdev)
{
g_mbox = NULL;
return 0;
}
static const struct of_device_id mtk_apu_mailbox_of_match[] = {
{ .compatible = "mediatek,mt8188-apu-mailbox" },
{}
};
MODULE_DEVICE_TABLE(of, mtk_apu_mailbox_of_match);
static struct platform_driver mtk_apu_mailbox_driver = {
.probe = mtk_apu_mailbox_probe,
.remove = mtk_apu_mailbox_remove,
.driver = {
.name = "mtk-apu-mailbox",
.of_match_table = mtk_apu_mailbox_of_match,
},
};
module_platform_driver(mtk_apu_mailbox_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MediaTek APU Mailbox Driver");