blob: 64d942d425b7481cb1040f88b01e16ad46d0cd05 [file] [log] [blame]
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)
*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/mailbox_client.h>
#include "meson_mhu.h"
#include "../firmware/bl40_module.h"
u32 num_scp_chans;
u32 send_listen_chans;
u32 isr_send;
u32 isr_m4;
#define DRIVER_NAME "meson_mhu"
#define MHU_BUFFER_SIZE 0x100
/*
* +--------------------+-------+---------------+
* | Hardware Register | Offset| Driver View |
* +--------------------+-------+---------------+
* | SCP_INTR_L_STAT | 0x014 | RX_STATUS(L) |
* | SCP_INTR_L_SET | 0x010 | RX_SET(L) |
* | SCP_INTR_L_CLEAR | 0x018 | RX_CLEAR(L) |
* +--------------------+-------+---------------+
* | SCP_INTR_H_STAT | 0x020 | RX_STATUS(H) |
* | SCP_INTR_H_SET | 0x01c | RX_SET(H) |
* | SCP_INTR_H_CLEAR | 0x024 | RX_CLEAR(H) |
* +--------------------+-------+---------------+
* | CPU_INTR_L_STAT | 0x038 | TX_STATUS(L) |
* | CPU_INTR_L_SET | 0x034 | TX_SET(L) |
* | CPU_INTR_L_CLEAR | 0x03c | TX_CLEAR(L) |
* +--------------------+-------+---------------+
* | CPU_INTR_H_STAT | 0x044 | TX_STATUS(H) |
* | CPU_INTR_H_SET | 0x040 | TX_SET(H) |
* | CPU_INTR_H_CLEAR | 0x048 | TX_CLEAR(H) |
* +--------------------+-------+---------------+
*/
#define RX_OFFSET(chan) (0x10 + (chan) * 0xc)
#define RX_STATUS(chan) (RX_OFFSET(chan) + 0x4)
#define RX_SET(chan) RX_OFFSET(chan)
#define RX_CLEAR(chan) (RX_OFFSET(chan) + 0x8)
#define TX_OFFSET(chan) (0x34 + (chan) * 0xc)
#define TX_STATUS(chan) (TX_OFFSET(chan) + 0x4)
#define TX_SET(chan) TX_OFFSET(chan)
#define TX_CLEAR(chan) (TX_OFFSET(chan) + 0x8)
/*
* +---------------+-------+----------------+
* | Payload | Offset| Driver View |
* +---------------+-------+----------------+
* | SCP->AP Low | 0x000 | RX_PAYLOAD(L) |
* | SCP->AP High | 0x400 | RX_PAYLOAD(H) |
* +---------------+-------+----------------+
* | AP->SCP Low | 0x200 | TX_PAYLOAD(H) |
* | AP->SCP High | 0x600 | TX_PAYLOAD(H) |
* +---------------+-------+----------------+
*/
#define PAYLOAD_MAX_SIZE 0x200
#define PAYLOAD_OFFSET 0x400
#define RX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET)
#define TX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET + PAYLOAD_MAX_SIZE)
struct mhu_ctlr {
struct device *dev;
void __iomem *mbox_base;
void __iomem *payload_base;
struct mbox_controller mbox_con;
struct mhu_chan *channels;
};
void bl40_rx_callback(struct mbox_client *cl, void *msg)
{
struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
pr_debug("call %s\n", __func__);
bl40_rx_msg(data->rx_buf, data->rx_size);
}
static irqreturn_t mbox_handler(int irq, void *p)
{
struct mbox_chan *link = (struct mbox_chan *)p;
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
void __iomem *payload = ctlr->payload_base;
int idx = chan->index;
struct mhu_data_buf *data;
u32 status = 0;
u32 is_send_isr = BIT(idx) & isr_send;
u32 is_send_chan = BIT(idx) & send_listen_chans;
if (isr_m4) {
if (BIT(idx) & isr_m4)
idx = 1;
else
idx = 0;
}
if (is_send_isr)
status = 1;
else
status = readl(mbox_base + RX_STATUS(idx));
pr_debug("isr %d idx %x sts %x\n", irq, idx, status);
if (status && irq == chan->rx_irq) {
data = chan->data;
if (!data) {
pr_err("data is null\n");
return IRQ_NONE; /* spurious */
}
if (data->rx_buf) {
if (is_send_isr) {
memcpy_fromio(data->rx_buf,
payload + TX_PAYLOAD(idx),
data->rx_size);
} else {
/*
* idx = 1 & to scp chans = 1 mailbox to m4
* need to get size, to m3 only low no need to get size
* idx = 1 & to scp chans = 2 mailbox no to m4
* mailbox chan low high all to m3, no need get size
*/
if (idx && num_scp_chans != CHANNEL_MAX)
data->rx_size =
readl(mbox_base + RX_STATUS(idx));
memcpy_fromio(data->rx_buf,
payload + RX_PAYLOAD(idx),
data->rx_size);
}
}
mbox_chan_received_data(link, data);
if (!is_send_isr)
writel(~0, mbox_base + RX_CLEAR(idx));
if (is_send_chan)
chan->data = NULL;
}
return IRQ_HANDLED;
}
static int mhu_send_data(struct mbox_chan *link, void *msg)
{
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
void __iomem *payload = ctlr->payload_base;
struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
int idx = chan->index;
if (!data)
return -EINVAL;
chan->data = data;
if (isr_m4) {
if (BIT(idx) & isr_m4)
idx = 1;
else
idx = 0;
}
if (data->tx_buf) {
memset_io(payload + TX_PAYLOAD(idx),
0, data->tx_size);
memcpy_toio(payload + TX_PAYLOAD(idx),
data->tx_buf, data->tx_size);
}
writel(data->cmd, mbox_base + TX_SET(idx));
return 0;
}
static int mhu_startup(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
int err, mbox_irq = chan->rx_irq;
err = request_threaded_irq(mbox_irq, mbox_handler,
NULL, IRQF_ONESHOT,
DRIVER_NAME, link);
return err;
}
static void mhu_shutdown(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
chan->data = NULL;
free_irq(chan->rx_irq, link);
}
static bool mhu_last_tx_done(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
int idx = chan->index;
return !readl(mbox_base + TX_STATUS(idx));
}
static 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 platform_device *pdev)
{
struct mhu_ctlr *ctlr;
struct mhu_chan *chan;
struct device *dev = &pdev->dev;
struct mbox_chan *l;
struct resource *res;
struct mbox_client *cl;
int idx, err;
u32 mbox_chans = 0;
int bit_chans = 0;
static const char * const channel_names[] = {
CHANNEL_LOW_PRIORITY,
CHANNEL_HIGH_PRIORITY
};
pr_debug("mhu to scp init\n");
ctlr = devm_kzalloc(dev, sizeof(*ctlr), GFP_KERNEL);
if (!ctlr)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "failed to get mailbox memory resource\n");
return -ENXIO;
}
ctlr->mbox_base = devm_ioremap_resource(dev, res);
if (IS_ERR(ctlr->mbox_base))
return PTR_ERR(ctlr->mbox_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(dev, "failed to get payload memory resource\n");
return -ENXIO;
}
ctlr->payload_base = devm_ioremap_resource(dev, res);
if (IS_ERR(ctlr->payload_base))
return PTR_ERR(ctlr->payload_base);
ctlr->dev = dev;
platform_set_drvdata(pdev, ctlr);
num_scp_chans = 0;
of_property_read_u32(dev->of_node, "num-chans-to-scp", &num_scp_chans);
if (num_scp_chans == 0 || num_scp_chans > 2)
num_scp_chans = CHANNEL_MAX;
send_listen_chans = 0;
of_property_read_u32(dev->of_node, "send-isr-bits", &send_listen_chans);
of_property_read_u32(dev->of_node, "ack-isr-bits", &isr_send);
of_property_read_u32(dev->of_node, "m4-isr-bits", &isr_m4);
of_property_read_u32(dev->of_node, "mbox-chans", &mbox_chans);
if (!mbox_chans)
mbox_chans = CHANNEL_MAX;
l = devm_kzalloc(dev, sizeof(*l) * mbox_chans, GFP_KERNEL);
if (!l)
return -ENOMEM;
ctlr->channels = devm_kzalloc(dev,
sizeof(struct mhu_chan) * mbox_chans,
GFP_KERNEL);
if (!ctlr->channels)
return -ENOMEM;
ctlr->mbox_con.chans = l;
ctlr->mbox_con.num_chans = mbox_chans;
ctlr->mbox_con.txdone_irq = true;
ctlr->mbox_con.ops = &mhu_ops;
ctlr->mbox_con.dev = dev;
for (idx = 0; idx < mbox_chans; idx++) {
chan = &ctlr->channels[idx];
chan->index = idx;
chan->ctlr = ctlr;
chan->rx_irq = platform_get_irq(pdev, idx);
if (chan->rx_irq < 0) {
dev_err(dev, "failed to get interrupt for %s\n",
channel_names[idx]);
return -ENXIO;
}
l[idx].con_priv = chan;
bit_chans |= BIT(idx);
}
if (mbox_controller_register(&ctlr->mbox_con)) {
dev_err(dev, "failed to register mailbox controller\n");
return -ENOMEM;
}
if (!send_listen_chans)
goto probe_done;
for (idx = 0; idx < mbox_chans; idx++) {
if (BIT(idx) & send_listen_chans)
continue;
cl = devm_kzalloc(dev, sizeof(struct mbox_client),
GFP_KERNEL);
cl->dev = dev;
cl->rx_callback = bl40_rx_callback;
l[idx].cl = cl;
chan = &ctlr->channels[idx];
chan->data = devm_kzalloc(dev,
sizeof(struct mhu_data_buf),
GFP_KERNEL);
chan->data->rx_buf = devm_kzalloc(dev,
MHU_BUFFER_SIZE,
GFP_KERNEL);
if (!chan->data->rx_buf)
return -ENOMEM;
chan->data->rx_size = MHU_BUFFER_SIZE;
err = request_threaded_irq(chan->rx_irq, mbox_handler,
NULL, IRQF_ONESHOT, DRIVER_NAME,
&l[idx]);
if (err) {
dev_err(dev, "request irq error\n");
return err;
}
}
probe_done:
mhu_device = dev;
/*set mhu type*/
mhu_f |= MASK_MHU;
pr_info("mhu %pK to scp done 0x%x\n", mhu_device, mhu_f);
return 0;
}
static int mhu_remove(struct platform_device *pdev)
{
struct mhu_ctlr *ctlr = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
mbox_controller_unregister(&ctlr->mbox_con);
devm_kfree(dev, ctlr->mbox_con.chans);
devm_iounmap(dev, ctlr->payload_base);
devm_iounmap(dev, ctlr->mbox_base);
platform_set_drvdata(pdev, NULL);
devm_kfree(dev, ctlr);
return 0;
}
static const struct of_device_id mhu_of_match[] = {
{ .compatible = "amlogic, meson_mhu" },
{},
};
static struct platform_driver mhu_driver = {
.probe = mhu_probe,
.remove = mhu_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = mhu_of_match,
},
};
static int __init mhu_init(void)
{
return platform_driver_register(&mhu_driver);
}
core_initcall(mhu_init);
static void __exit mhu_exit(void)
{
platform_driver_unregister(&mhu_driver);
}
module_exit(mhu_exit);
MODULE_AUTHOR("yan wang <yan.wang@amlogic.com>");
MODULE_DESCRIPTION("MESON MHU mailbox driver");
MODULE_LICENSE("GPL");