pi-linux/bsp/drivers/msgbox/sunxi-soft-msgbox.c

536 lines
14 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2024 - 2028 Allwinner Technology Co.,Ltd. All rights reserved. */
/*
* Copyright (c) 2024-2028 allwinnertech Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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/of_address.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#define BOOT_MAGIC (0x91287346)
#define NUM_OS (4)
#define NUM_CHAN (4)
#define NUM_FIFO (8)
#define NUM_SLOT (NUM_OS * NUM_OS * NUM_CHAN)
/* gic macro */
#define IPC_IRQ_NONE (0)
#define GICD_ISPENDR_BASE (0x200)
#define GICD_ICPENDR_BASE (0X280)
#define OFFSET(base, irq) (base + (irq >> 5) * 4)
#define BIT_VAL(irq) (1 << (irq % 32))
#define mbox_dbg(mbox, ...) dev_dbg((mbox)->controller.dev, __VA_ARGS__)
enum direction {
SEND = 0,
RECV
};
/*
* os_state: local os if data need recv, local os read, remote os writel;
* chan_state: tx rx is chan fifo ptr, tx update by remote os, rx update by local os;
* chan_fifo[slot_id][fifo_id]: use for data buffer
*/
typedef struct {
u32 state[NUM_OS];
u32 boot[NUM_OS];
struct {
u32 tx;
u32 rx;
} slot_state[NUM_SLOT];
u32 fifo[NUM_SLOT][NUM_FIFO];
} chan_buffer_t;
struct sunxi_mbox_chan {
struct sunxi_irq_mbox *parent;
int chan_id;
int remote_id;
int local_id;
int tx_irq;
int rx_irq;
int slot_rx;
int slot_tx;
bool used;
};
struct sunxi_irq_mbox {
struct mbox_controller controller;
struct mbox_chan *chan;
struct sunxi_mbox_chan *mchan;
int num_chans;
spinlock_t lock;
void __iomem *mem_virt;
unsigned long mem_phys;
unsigned long mem_size;
void __iomem *gic_dbase;
int recv_irqs[NUM_OS];
int irq_num;
int rx_irq;
int local_id;
};
static bool sunxi_irq_mbox_last_tx_done(struct mbox_chan *mchan);
static bool sunxi_irq_mbox_peek_data(struct mbox_chan *chan);
/* I/O memory map interrupt controller register space */
static int sunxi_intc_map(struct sunxi_irq_mbox *mbox)
{
struct device_node *gic_node = NULL;
int err = 0;
struct resource res;
/* get gic node from DT */
gic_node = of_parse_phandle(mbox->controller.dev->of_node, "gic-node", 0);
if (!gic_node) {
pr_err("Unable to find gic-node in device tree\n");
err = -ENXIO;
goto ret_err;
}
/* get base address from DT node*/
err = of_address_to_resource(gic_node, 0, &res);
if (err) {
pr_err("Unable to get resource from gic node \n");
err = -ENOMEM;
goto ret_err;
}
/* map gic distributer register space */
mbox->gic_dbase = (void __iomem *)ioremap(res.start, resource_size(&res));
if (!mbox->gic_dbase) {
pr_err("Unable to ioremap gic distributer register space \n");
err = -ENOMEM;
goto ret_err;
}
ret_err:
/* release refcount to gic DT node */
of_node_put(gic_node);
return err;
}
/* I/O memory unmap interrupt controller register space */
static inline void sunxi_intc_unmap(struct sunxi_irq_mbox *mbox)
{
iounmap((void *)mbox->gic_dbase);
}
static inline void sunxi_mbox_irq_notify(struct sunxi_mbox_chan *mchan)
{
int irq = mchan->tx_irq;
struct sunxi_irq_mbox *mbox = mchan->parent;
chan_buffer_t *chan_buf = (chan_buffer_t *)mbox->mem_virt;
/* TODO */
while (BOOT_MAGIC != readl((void *)&chan_buf->boot[mchan->remote_id]))
;;
writel(BIT_VAL(irq), mbox->gic_dbase + OFFSET(GICD_ISPENDR_BASE, irq));
}
static inline void sunxi_mbox_irq_clear(struct sunxi_mbox_chan *mchan)
{
struct sunxi_irq_mbox *mbox = mchan->parent;
int irq = mbox->rx_irq;
writel(BIT_VAL(irq), mbox->gic_dbase + OFFSET(GICD_ICPENDR_BASE, irq));
}
static inline void *os_state_addr(struct sunxi_mbox_chan *mchan, enum direction d)
{
struct sunxi_irq_mbox *mbox = mchan->parent;
chan_buffer_t *buffer = (chan_buffer_t *)mbox->mem_virt;
if (SEND == d)
return (void *)&buffer->state[mchan->remote_id];
else
return (void *)&buffer->state[mchan->local_id];
}
static inline void *chan_state_tx_addr(struct sunxi_mbox_chan *mchan, enum direction d)
{
struct sunxi_irq_mbox *mbox = mchan->parent;
chan_buffer_t *buffer = (chan_buffer_t *)mbox->mem_virt;
if (SEND == d)
return (void *)&buffer->slot_state[mchan->slot_tx];
else
return (void *)&buffer->slot_state[mchan->slot_rx];
}
static inline void *chan_state_rx_addr(struct sunxi_mbox_chan *mchan, enum direction d)
{
return (void *)((int *)chan_state_tx_addr(mchan, d) + 1);
}
static unsigned long chan_fifo_addr(struct sunxi_mbox_chan *mchan, enum direction d)
{
unsigned long base;
unsigned long offset;
struct sunxi_irq_mbox *mbox = mchan->parent;
chan_buffer_t *buffer = (chan_buffer_t *)mbox->mem_virt;
int tx_idx = readl(chan_state_tx_addr(mchan, d));
int rx_idx = readl(chan_state_rx_addr(mchan, d));
pr_debug("fifo tx index = %d, rx index = %d \n", tx_idx, rx_idx);
if (SEND == d) {
/* addr = (unsigned long)&buffer->fifo[mchan->slot_tx][tx_idx]; */
offset = (mchan->slot_tx * NUM_FIFO + tx_idx) * sizeof(u32);
tx_idx = (tx_idx + 1) % NUM_FIFO;
writel(tx_idx, chan_state_tx_addr(mchan, d));
} else {
/* addr = (unsigned long)&buffer->fifo[mchan->slot_rx][rx_idx]; */
offset = (mchan->slot_rx * NUM_FIFO + rx_idx) * sizeof(u32);
rx_idx = (rx_idx + 1) % NUM_FIFO;
writel(rx_idx, chan_state_rx_addr(mchan, d));
}
base = (unsigned long)buffer->fifo;
return (base + offset);
}
static struct mbox_chan *sunxi_mbox_xlate(struct mbox_controller *controller,
const struct of_phandle_args *spec)
{
struct sunxi_irq_mbox *mbox = dev_get_drvdata(controller->dev);
struct sunxi_mbox_chan *mchan;
struct mbox_chan *chan;
int idx = 0;
unsigned int os_id = spec->args[0];
unsigned int chan_id = spec->args[1];
/* Bounds checking */
if (os_id >= NUM_OS || chan_id >= NUM_CHAN) {
pr_err("Invalid os idx %d channel %d \n", os_id, chan_id);
return ERR_PTR(-EINVAL);
}
/* Is requested channel free? */
idx = (mbox->local_id * NUM_OS + os_id) * NUM_CHAN + chan_id;
chan = &mbox->chan[idx];
mchan = chan->con_priv;
if (mchan->used) {
pr_err("os(%d) Channel(%d) in use\n", os_id, chan_id);
return ERR_PTR(-EBUSY);
}
mchan->remote_id = os_id;
mchan->local_id = mbox->local_id;
mchan->chan_id = chan_id;
mchan->tx_irq = mbox->recv_irqs[os_id];
mchan->rx_irq = mbox->recv_irqs[mbox->local_id];
mchan->slot_tx = (os_id * NUM_OS + mbox->local_id) * NUM_CHAN + chan_id;
mchan->used = 1;
pr_info("request local_os(%d) <==> remote_os(%d), chan_id(%d) \n",
mchan->local_id, mchan->remote_id, mchan->chan_id);
pr_debug("rx slot(%d), tx slot(%d) \n", mchan->slot_rx, mchan->slot_tx);
return chan;
}
static irqreturn_t sunxi_irq_mbox_handler(int irq, void *dev_id)
{
struct sunxi_irq_mbox *mbox = dev_id;
chan_buffer_t *buffer = NULL;
struct mbox_chan *chan = NULL;
struct sunxi_mbox_chan *mchan = NULL;
uint32_t status;
int n, s, e;
uint32_t msg;
void *addr = NULL;
s = mbox->local_id * NUM_OS * NUM_CHAN;
e = s + (NUM_OS * NUM_CHAN);
buffer = (chan_buffer_t *)mbox->mem_virt;
/* Only examine channels that are currently enabled. */
status = readl((void *)&buffer->state[mbox->local_id]);
if (!(status))
goto ret;
for (n = s; n < e; ++n) {
chan = &mbox->chan[n];
mchan = (struct sunxi_mbox_chan *)chan->con_priv;
if (!mchan->used) /* not register, will continue */
continue;
while (sunxi_irq_mbox_peek_data(chan)) { /* Todo: if always data avaliable, how to do */
addr = (void *)chan_fifo_addr(mchan, RECV);
msg = readl(addr);
pr_debug("--- [recv] os(%d) ==> 0s(%d) chan_id %d data 0x%x\n",
mchan->remote_id, mchan->local_id,
mchan->chan_id, msg);
mbox_chan_received_data(chan, &msg);
}
writel(0, os_state_addr(mchan, RECV));
}
ret:
/* clear irq pending. */
sunxi_mbox_irq_clear(chan->con_priv);
return IRQ_HANDLED;
}
static int sunxi_irq_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct sunxi_mbox_chan *mchan = chan->con_priv;
uint32_t msg = *(uint32_t *)data;
void *addr = NULL;
/* send data to tx chan fifo */
pr_debug("--- [send] os(%d) os(%d) chan_id(%d) msg = %d\n",
mchan->local_id, mchan->remote_id,
mchan->chan_id, msg);
addr = (void *)chan_fifo_addr(mchan, SEND);
writel(msg, addr);
writel(1, os_state_addr(mchan, SEND));
/* raise irq to remote */
sunxi_mbox_irq_notify(mchan); /* TODO */
return 0;
}
static int sunxi_irq_mbox_startup(struct mbox_chan *chan)
{
/* Todo */
return 0;
}
static void sunxi_irq_mbox_shutdown(struct mbox_chan *chan)
{
/* Todo */
}
static bool sunxi_irq_mbox_last_tx_done(struct mbox_chan *chan)
{
struct sunxi_mbox_chan *mchan = chan->con_priv;
int tx_idx = readl(chan_state_tx_addr(mchan, SEND));
int rx_idx = readl(chan_state_rx_addr(mchan, SEND));
return ((tx_idx + 1) % NUM_FIFO != rx_idx);
}
static bool sunxi_irq_mbox_peek_data(struct mbox_chan *chan)
{
struct sunxi_mbox_chan *mchan = chan->con_priv;
int tx_idx = readl(chan_state_tx_addr(mchan, RECV));
int rx_idx = readl(chan_state_rx_addr(mchan, RECV));
return (tx_idx != rx_idx);
}
static const struct mbox_chan_ops sunxi_irq_mbox_chan_ops = {
.send_data = sunxi_irq_mbox_send_data,
.startup = sunxi_irq_mbox_startup,
.shutdown = sunxi_irq_mbox_shutdown,
.last_tx_done = sunxi_irq_mbox_last_tx_done,
.peek_data = sunxi_irq_mbox_peek_data,
};
static int sunxi_irq_mbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource res;
struct sunxi_irq_mbox *mbox;
struct device_node *node;
void *zero_buffer = NULL;
unsigned int zero_size = 0;
int i;
int ret = 0;
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
if (!mbox)
return -ENOMEM;
mbox->num_chans = NUM_SLOT;
mbox->chan = devm_kcalloc(dev, mbox->num_chans, sizeof(*mbox->chan), GFP_KERNEL);
if (!mbox->chan)
return -ENOMEM;
mbox->mchan = devm_kcalloc(dev,
mbox->num_chans, sizeof(*mbox->mchan), GFP_KERNEL);
if (!mbox->mchan)
return -ENOMEM;
for (i = 0; i < mbox->num_chans; i++) {
mbox->chan[i].con_priv = &mbox->mchan[i];
mbox->mchan[i].parent = mbox;
mbox->mchan[i].slot_rx = i;
mbox->mchan[i].used = 0;
}
ret = of_property_read_variable_u32_array(dev->of_node, "recv-irqs", mbox->recv_irqs, 2, NUM_OS);
if (ret < 0) {
pr_err("Unable to find recv-irqs in node \n");
return ret;
}
for (i = 0; i < NUM_OS; i++) {
pr_debug("OS(%d) recv_irq is %d \n", i, mbox->recv_irqs[i]);
}
ret = of_property_read_u32_index(dev->of_node, "local-id", 0, &mbox->local_id);
if (ret) {
pr_err("Unable to find local_id in node \n");
return -EINVAL;
}
mbox->rx_irq = mbox->recv_irqs[mbox->local_id];
node = of_parse_phandle(dev->of_node, "memory-mbox", 0);
if (!node) {
dev_err(dev, "no memory-mbox specified \n");
return -EINVAL;
}
ret = of_address_to_resource(node, 0, &res);
of_node_put(node);
if (ret)
return ret;
mbox->mem_phys = res.start;
mbox->mem_size = resource_size(&res);
mbox->mem_virt = devm_ioremap_wc(dev, mbox->mem_phys, mbox->mem_size);
if (IS_ERR(mbox->mem_virt)) {
dev_err(dev, "Failed to map memory fifo: %pa+%zx\n",
&mbox->mem_phys, mbox->mem_size);
return -EBUSY;
}
zero_size = sizeof(u32) * (NUM_OS + NUM_SLOT) * 2;
zero_buffer = kzalloc(zero_size, GFP_KERNEL);
if (!zero_buffer) {
dev_err(dev, "Failed to get memory \n");
return -ENOMEM;
}
memcpy_toio(mbox->mem_virt, zero_buffer, zero_size);
kfree(zero_buffer);
pr_debug("---- buffer phys addr : %lx ----\n", mbox->mem_phys);
pr_debug("---- buffer virt addr : %lx ----\n", (long)mbox->mem_virt);
mbox->controller.dev = dev;
mbox->controller.ops = &sunxi_irq_mbox_chan_ops;
mbox->controller.chans = mbox->chan;
mbox->controller.num_chans = mbox->num_chans;
mbox->controller.txdone_irq = false;
mbox->controller.txdone_poll = true;
mbox->controller.txpoll_period = 5;
mbox->controller.of_xlate = sunxi_mbox_xlate;
if (sunxi_intc_map(mbox)) {
pr_err("Failed to map intc MMIO resource: %d\n", ret);
goto ioremap_err;
}
ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0),
sunxi_irq_mbox_handler, 0, dev_name(dev), mbox);
if (ret) {
dev_err(dev, "Failed to register IRQ handler: %d\n", ret);
return ret;
}
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);
return ret;
}
pr_info("----- irq msgbox probe success -----\n");
ioremap_err:
return ret;
}
static int sunxi_irq_mbox_remove(struct platform_device *pdev)
{
struct sunxi_irq_mbox *mbox = platform_get_drvdata(pdev);
mbox_controller_unregister(&mbox->controller);
return 0;
}
static const struct of_device_id sunxi_irq_mbox_of_match[] = {
{ .compatible = "allwinner,irq-msgbox", },
{},
};
MODULE_DEVICE_TABLE(of, sunxi_irq_mbox_of_match);
static struct platform_driver sunxi_irq_mbox_driver = {
.driver = {
.name = "sunxi-soft-msgbox",
.of_match_table = sunxi_irq_mbox_of_match,
},
.probe = sunxi_irq_mbox_probe,
.remove = sunxi_irq_mbox_remove,
};
module_platform_driver(sunxi_irq_mbox_driver);
MODULE_AUTHOR("sw1@allwinnertech.com");
MODULE_DESCRIPTION("Sunxi irq Message Box driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0.0");