Files
kernel-starfive-jh7110/drivers/mailbox/starfive_ipi_mailbox.c
Minda Chen be63d21ec8 mailbox: ipi: Add mem base ipi mailbox driver
Add memory base mailbox driver which used by rpmsg driver.

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
2024-05-31 17:29:13 +08:00

306 lines
6.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2024 StarFive Technology Co., Ltd.
*/
#include <asm/irq.h>
#include <asm/sbi.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/bitfield.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/suspend.h>
#include <linux/slab.h>
#include "mailbox.h"
#define IPI_MB_CHANS 2
#define IPI_MB_DEV_PER_CHAN 8
#define TX_MBOX_OFFSET 0x400
#define TX_DONE_OFFSET 0x100
#define QUEUE_ID_OFFSET 16
#define QUEUE_TO_CHAN 4
/* Please not change TX & RX */
enum ipi_mb_chan_type {
IPI_MB_TYPE_RX = 0, /* Rx */
IPI_MB_TYPE_TX = 1, /* Txdone */
};
struct ipi_mb_con_priv {
unsigned int idx;
enum ipi_mb_chan_type type;
struct mbox_chan *chan;
struct tasklet_struct txdb_tasklet;
int rtos_hart_id;
};
struct ipi_mb_priv {
struct device *dev;
struct mbox_controller mbox;
struct mbox_chan chans[IPI_MB_CHANS * 2];
struct ipi_mb_con_priv con_priv_tx[IPI_MB_CHANS];
struct ipi_mb_con_priv con_priv_rx[IPI_MB_CHANS];
void *tx_mbase;
void *rx_mbase;
int mem_size;
int dev_num_per_chan;
};
struct ipi_mb_priv *mb_priv;
static struct ipi_mb_priv *to_ipi_mb_priv(struct mbox_controller *mbox)
{
return container_of(mbox, struct ipi_mb_priv, mbox);
}
static int ipi_mb_generic_tx(struct ipi_mb_priv *priv,
struct ipi_mb_con_priv *cp,
void *data)
{
unsigned long *msg = data, *mb_base;
unsigned long queue;
switch (cp->type) {
case IPI_MB_TYPE_TX:
queue = *msg >> (QUEUE_ID_OFFSET + 1);
if (FIELD_GET(BIT(QUEUE_ID_OFFSET), *msg)) {
mb_base = mb_priv->tx_mbase;
WARN_ON(mb_base[queue]);
xchg(&mb_base[queue], *msg);
sbi_send_ipi_amp(cp->rtos_hart_id, IPI_MB_TYPE_TX); /* revert it */
} else {
mb_base = mb_priv->tx_mbase + TX_DONE_OFFSET;
WARN_ON(mb_base[queue]);
xchg(&mb_base[queue], *msg);
sbi_send_ipi_amp(cp->rtos_hart_id, IPI_MB_TYPE_RX);
tasklet_schedule(&cp->txdb_tasklet);
}
break;
default:
dev_warn_ratelimited(priv->dev,
"Send data on wrong channel type: %d\n", cp->type);
return -EINVAL;
}
return 0;
}
static struct mbox_chan *queue_to_channel(unsigned long msg, bool tx)
{
int index;
int offset = QUEUE_ID_OFFSET + QUEUE_TO_CHAN;
index = (tx) ? (msg >> offset) + IPI_MB_CHANS : (msg >> offset);
return &mb_priv->chans[index];
}
static void ipi_mb_isr(unsigned long msg_type)
{
unsigned long *mb_base, msg;
struct mbox_chan *chan;
void *rx_done_base;
u32 i;
if (!msg_type)
return;
mb_base = mb_priv->rx_mbase;
rx_done_base = mb_priv->rx_mbase + TX_DONE_OFFSET;
if (msg_type & BIT(IPI_MB_TYPE_RX)) {
for (i = 0; i < IPI_MB_CHANS * mb_priv->dev_num_per_chan; i++) {
msg = xchg(&mb_base[i], 0);
chan = queue_to_channel(msg, 0);
if (msg)
mbox_chan_received_data(chan, (void *)&msg);
}
}
if (msg_type & BIT(IPI_MB_TYPE_TX)) {
mb_base = rx_done_base;
for (i = 0; i < IPI_MB_CHANS * mb_priv->dev_num_per_chan; i++) {
msg = xchg(&mb_base[i], 0);
chan = queue_to_channel(msg, 1);
if (msg) {
mbox_chan_received_data(chan, (void *)&msg);
mbox_chan_txdone(chan, 0);
}
}
}
}
static int ipi_mb_send_data(struct mbox_chan *chan, void *data)
{
struct ipi_mb_priv *priv = to_ipi_mb_priv(chan->mbox);
struct ipi_mb_con_priv *cp = chan->con_priv;
return ipi_mb_generic_tx(priv, cp, data);
}
static void ipi_mb_txdb_tasklet(unsigned long data)
{
struct ipi_mb_con_priv *cp = (struct ipi_mb_con_priv *)data;
mbox_chan_txdone(cp->chan, 0);
}
static const struct mbox_chan_ops ipi_mb_ops = {
.send_data = ipi_mb_send_data,
};
static struct mbox_chan *ipi_mb_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *sp)
{
struct mbox_chan *p_chan;
u32 type, idx;
if (sp->args_count != 2) {
dev_err(mbox->dev, "Invalid argument count %d\n", sp->args_count);
return ERR_PTR(-EINVAL);
}
type = sp->args[0]; /* channel type */
idx = sp->args[1]; /* index */
if (idx >= (mbox->num_chans >> 1)) {
dev_err(mbox->dev,
"Not supported channel number: %d. (type: %d, idx: %d)\n",
idx, type, idx);
return ERR_PTR(-EINVAL);
}
if (type == IPI_MB_TYPE_TX)
p_chan = &mbox->chans[idx + IPI_MB_CHANS];
else
p_chan = &mbox->chans[idx];
return p_chan;
}
static void ipi_mb_init_generic(struct ipi_mb_priv *priv, int rtos_hart_id)
{
unsigned int i;
for (i = 0; i < IPI_MB_CHANS; i++) {
struct ipi_mb_con_priv *cp = &priv->con_priv_tx[i];
cp->idx = i;
cp->type = IPI_MB_TYPE_TX;
cp->chan = &priv->chans[i + IPI_MB_CHANS];
cp->rtos_hart_id = rtos_hart_id;
tasklet_init(&cp->txdb_tasklet, ipi_mb_txdb_tasklet,
(unsigned long)cp);
cp->chan->con_priv = cp;
}
for (i = 0; i < IPI_MB_CHANS; i++) {
struct ipi_mb_con_priv *cp = &priv->con_priv_rx[i];
cp->idx = i;
cp->type = IPI_MB_TYPE_RX;
cp->chan = &priv->chans[i];
cp->rtos_hart_id = rtos_hart_id;
cp->chan->con_priv = cp;
}
priv->mbox.num_chans = IPI_MB_CHANS * 2;
priv->mbox.of_xlate = ipi_mb_xlate;
priv->dev_num_per_chan = IPI_MB_DEV_PER_CHAN;
}
static int ipi_mb_init_mem_region(struct ipi_mb_priv *priv, struct platform_device *pdev)
{
phys_addr_t phy_addr;
struct resource *r;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
phy_addr = r->start;
priv->mem_size = resource_size(r);
priv->rx_mbase = devm_memremap(priv->dev, phy_addr,
priv->mem_size,
MEMREMAP_WB);
if (IS_ERR(priv->rx_mbase)) {
dev_err(priv->dev, "unable to map memory region: %llx %d\n",
(u64)r->start, priv->mem_size);
return -EBUSY;
}
priv->tx_mbase = priv->rx_mbase + TX_MBOX_OFFSET;
memset(priv->rx_mbase, 0, priv->mem_size);
return 0;
}
static int starfive_ipi_mb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct ipi_mb_priv *priv;
u32 rtos_hart_id;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
if (of_property_read_u32(np, "rtos-hart-id",
&rtos_hart_id))
return -EINVAL;
priv->dev = dev;
priv->mbox.dev = dev;
priv->mbox.ops = &ipi_mb_ops;
priv->mbox.chans = priv->chans;
priv->mbox.txdone_irq = true;
ipi_mb_init_generic(priv, rtos_hart_id);
platform_set_drvdata(pdev, priv);
ret = ipi_mb_init_mem_region(priv, pdev);
if (ret)
return ret;
register_ipi_mailbox_handler(ipi_mb_isr);
mb_priv = priv;
ret = devm_mbox_controller_register(priv->dev, &priv->mbox);
return ret;
}
static const struct of_device_id ipi_amp_of_match[] = {
{ .compatible = "starfive,ipi-amp-mailbox", .data = NULL },
{},
};
MODULE_DEVICE_TABLE(of, amp_rpmsg_of_match);
static struct platform_driver starfive_ipi_mb_driver = {
.probe = starfive_ipi_mb_probe,
.driver = {
.name = "starfive-ipi-mailbox",
.of_match_table = ipi_amp_of_match,
},
};
module_platform_driver(starfive_ipi_mb_driver);
MODULE_LICENSE("GPL");