// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2021 Alibaba Group Holding Limited .
*/
#include <linux/clk.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_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
/* Status Register */
#define TH_1520_MBOX_STA 0 x0
#define TH_1520_MBOX_CLR 0 x4
#define TH_1520_MBOX_MASK 0 xc
/* Transmit/receive data register:
* INFO0 ~ INFO6
*/
#define TH_1520_MBOX_INFO_NUM 8
#define TH_1520_MBOX_DATA_INFO_NUM 7
#define TH_1520_MBOX_INFO0 0 x14
/* Transmit ack register: INFO7 */
#define TH_1520_MBOX_INFO7 0 x30
/* Generate remote icu IRQ Register */
#define TH_1520_MBOX_GEN 0 x10
#define TH_1520_MBOX_GEN_RX_DATA BIT(6 )
#define TH_1520_MBOX_GEN_TX_ACK BIT(7 )
#define TH_1520_MBOX_CHAN_RES_SIZE 0 x1000
#define TH_1520_MBOX_CHANS 4
#define TH_1520_MBOX_CHAN_NAME_SIZE 20
#define TH_1520_MBOX_ACK_MAGIC 0 xdeadbeaf
#ifdef CONFIG_PM_SLEEP
/* store MBOX context across system-wide suspend/resume transitions */
struct th1520_mbox_context {
u32 intr_mask[TH_1520_MBOX_CHANS];
};
#endif
enum th1520_mbox_icu_cpu_id {
TH_1520_MBOX_ICU_KERNEL_CPU0, /* 910T */
TH_1520_MBOX_ICU_CPU1, /* 902 */
TH_1520_MBOX_ICU_CPU2, /* 906 */
TH_1520_MBOX_ICU_CPU3, /* 910R */
};
struct th1520_mbox_con_priv {
enum th1520_mbox_icu_cpu_id idx;
void __iomem *comm_local_base;
void __iomem *comm_remote_base;
char irq_desc[TH_1520_MBOX_CHAN_NAME_SIZE];
struct mbox_chan *chan;
};
struct th1520_mbox_priv {
struct device *dev;
void __iomem *local_icu[TH_1520_MBOX_CHANS];
void __iomem *remote_icu[TH_1520_MBOX_CHANS - 1 ];
void __iomem *cur_cpu_ch_base;
spinlock_t mbox_lock; /* control register lock */
struct mbox_controller mbox;
struct mbox_chan mbox_chans[TH_1520_MBOX_CHANS];
struct clk_bulk_data clocks[TH_1520_MBOX_CHANS];
struct th1520_mbox_con_priv con_priv[TH_1520_MBOX_CHANS];
int irq;
#ifdef CONFIG_PM_SLEEP
struct th1520_mbox_context *ctx;
#endif
};
static struct th1520_mbox_priv *
to_th1520_mbox_priv(struct mbox_controller *mbox)
{
return container_of(mbox, struct th1520_mbox_priv, mbox);
}
static void th1520_mbox_write(struct th1520_mbox_priv *priv, u32 val, u32 offs)
{
iowrite32(val, priv->cur_cpu_ch_base + offs);
}
static u32 th1520_mbox_read(struct th1520_mbox_priv *priv, u32 offs)
{
return ioread32(priv->cur_cpu_ch_base + offs);
}
static u32 th1520_mbox_rmw(struct th1520_mbox_priv *priv, u32 off, u32 set,
u32 clr)
{
unsigned long flags;
u32 val;
spin_lock_irqsave(&priv->mbox_lock, flags);
val = th1520_mbox_read(priv, off);
val &= ~clr;
val |= set;
th1520_mbox_write(priv, val, off);
spin_unlock_irqrestore(&priv->mbox_lock, flags);
return val;
}
static void th1520_mbox_chan_write(struct th1520_mbox_con_priv *cp, u32 val,
u32 offs, bool is_remote)
{
if (is_remote)
iowrite32(val, cp->comm_remote_base + offs);
else
iowrite32(val, cp->comm_local_base + offs);
}
static u32 th1520_mbox_chan_read(struct th1520_mbox_con_priv *cp, u32 offs,
bool is_remote)
{
if (is_remote)
return ioread32(cp->comm_remote_base + offs);
else
return ioread32(cp->comm_local_base + offs);
}
static void th1520_mbox_chan_rmw(struct th1520_mbox_con_priv *cp, u32 off,
u32 set, u32 clr, bool is_remote)
{
struct th1520_mbox_priv *priv = to_th1520_mbox_priv(cp->chan->mbox);
unsigned long flags;
u32 val;
spin_lock_irqsave(&priv->mbox_lock, flags);
val = th1520_mbox_chan_read(cp, off, is_remote);
val &= ~clr;
val |= set;
th1520_mbox_chan_write(cp, val, off, is_remote);
spin_unlock_irqrestore(&priv->mbox_lock, flags);
}
static void th1520_mbox_chan_rd_data(struct th1520_mbox_con_priv *cp,
void *data, bool is_remote)
{
u32 off = TH_1520_MBOX_INFO0;
u32 *arg = data;
u32 i;
/* read info0 ~ info6, totally 28 bytes
* requires data memory size is 28 bytes
*/
for (i = 0 ; i < TH_1520_MBOX_DATA_INFO_NUM; i++) {
*arg = th1520_mbox_chan_read(cp, off, is_remote);
off += 4 ;
arg++;
}
}
static void th1520_mbox_chan_wr_data(struct th1520_mbox_con_priv *cp,
void *data, bool is_remote)
{
u32 off = TH_1520_MBOX_INFO0;
u32 *arg = data;
u32 i;
/* write info0 ~ info6, totally 28 bytes
* requires data memory is 28 bytes valid data
*/
for (i = 0 ; i < TH_1520_MBOX_DATA_INFO_NUM; i++) {
th1520_mbox_chan_write(cp, *arg, off, is_remote);
off += 4 ;
arg++;
}
}
static void th1520_mbox_chan_wr_ack(struct th1520_mbox_con_priv *cp, void *data,
bool is_remote)
{
u32 off = TH_1520_MBOX_INFO7;
u32 *arg = data;
th1520_mbox_chan_write(cp, *arg, off, is_remote);
}
static int th1520_mbox_chan_id_to_mapbit(struct th1520_mbox_con_priv *cp)
{
int mapbit = 0 ;
int i;
for (i = 0 ; i < TH_1520_MBOX_CHANS; i++) {
if (i == cp->idx)
return mapbit;
if (i != TH_1520_MBOX_ICU_KERNEL_CPU0)
mapbit++;
}
if (i == TH_1520_MBOX_CHANS)
dev_err(cp->chan->mbox->dev, "convert to mapbit failed\n" );
return 0 ;
}
static irqreturn_t th1520_mbox_isr(int irq, void *p)
{
struct mbox_chan *chan = p;
struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox);
struct th1520_mbox_con_priv *cp = chan->con_priv;
int mapbit = th1520_mbox_chan_id_to_mapbit(cp);
u32 sta, dat[TH_1520_MBOX_DATA_INFO_NUM];
u32 ack_magic = TH_1520_MBOX_ACK_MAGIC;
u32 info0_data, info7_data;
sta = th1520_mbox_read(priv, TH_1520_MBOX_STA);
if (!(sta & BIT(mapbit)))
return IRQ_NONE;
/* clear chan irq bit in STA register */
th1520_mbox_rmw(priv, TH_1520_MBOX_CLR, BIT(mapbit), 0 );
/* info0 is the protocol word, should not be zero! */
info0_data = th1520_mbox_chan_read(cp, TH_1520_MBOX_INFO0, false );
if (info0_data) {
/* read info0~info6 data */
th1520_mbox_chan_rd_data(cp, dat, false );
/* clear local info0 */
th1520_mbox_chan_write(cp, 0 x0, TH_1520_MBOX_INFO0, false );
/* notify remote cpu */
th1520_mbox_chan_wr_ack(cp, &ack_magic, true );
/* CPU1 902/906 use polling mode to monitor info7 */
if (cp->idx != TH_1520_MBOX_ICU_CPU1 &&
cp->idx != TH_1520_MBOX_ICU_CPU2)
th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN,
TH_1520_MBOX_GEN_TX_ACK, 0 , true );
/* transfer the data to client */
mbox_chan_received_data(chan, (void *)dat);
}
/* info7 magic value mean the real ack signal, not generate bit7 */
info7_data = th1520_mbox_chan_read(cp, TH_1520_MBOX_INFO7, false );
if (info7_data == TH_1520_MBOX_ACK_MAGIC) {
/* clear local info7 */
th1520_mbox_chan_write(cp, 0 x0, TH_1520_MBOX_INFO7, false );
/* notify framework the last TX has completed */
mbox_chan_txdone(chan, 0 );
}
if (!info0_data && !info7_data)
return IRQ_NONE;
return IRQ_HANDLED;
}
static int th1520_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct th1520_mbox_con_priv *cp = chan->con_priv;
th1520_mbox_chan_wr_data(cp, data, true );
th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, TH_1520_MBOX_GEN_RX_DATA, 0 ,
true );
return 0 ;
}
static int th1520_mbox_startup(struct mbox_chan *chan)
{
struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox);
struct th1520_mbox_con_priv *cp = chan->con_priv;
u32 data[8 ] = {};
int mask_bit;
int ret;
/* clear local and remote generate and info0~info7 */
th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, 0 x0, 0 xff, true );
th1520_mbox_chan_rmw(cp, TH_1520_MBOX_GEN, 0 x0, 0 xff, false );
th1520_mbox_chan_wr_ack(cp, &data[7 ], true );
th1520_mbox_chan_wr_ack(cp, &data[7 ], false );
th1520_mbox_chan_wr_data(cp, &data[0 ], true );
th1520_mbox_chan_wr_data(cp, &data[0 ], false );
/* enable the chan mask */
mask_bit = th1520_mbox_chan_id_to_mapbit(cp);
th1520_mbox_rmw(priv, TH_1520_MBOX_MASK, BIT(mask_bit), 0 );
/*
* Mixing devm_ managed resources with manual IRQ handling is generally
* discouraged due to potential complexities with resource management ,
* especially when dealing with shared interrupts . However , in this case ,
* the approach is safe and effective because :
*
* 1 . Each mailbox channel requests its IRQ within the . startup ( ) callback
* and frees it within the . shutdown ( ) callback .
* 2 . During device unbinding , the devm_ managed mailbox controller first
* iterates through all channels , ensuring that their IRQs are freed before
* any other devm_ resources are released .
*
* This ordering guarantees that no interrupts can be triggered from the device
* while it is being unbound , preventing race conditions and ensuring system
* stability .
*/
ret = request_irq(priv->irq, th1520_mbox_isr,
IRQF_SHARED | IRQF_NO_SUSPEND, cp->irq_desc, chan);
if (ret) {
dev_err(priv->dev, "Unable to acquire IRQ %d\n" , priv->irq);
return ret;
}
return 0 ;
}
static void th1520_mbox_shutdown(struct mbox_chan *chan)
{
struct th1520_mbox_priv *priv = to_th1520_mbox_priv(chan->mbox);
struct th1520_mbox_con_priv *cp = chan->con_priv;
int mask_bit;
free_irq(priv->irq, chan);
/* clear the chan mask */
mask_bit = th1520_mbox_chan_id_to_mapbit(cp);
th1520_mbox_rmw(priv, TH_1520_MBOX_MASK, 0 , BIT(mask_bit));
}
static const struct mbox_chan_ops th1520_mbox_ops = {
.send_data = th1520_mbox_send_data,
.startup = th1520_mbox_startup,
.shutdown = th1520_mbox_shutdown,
};
static int th1520_mbox_init_generic(struct th1520_mbox_priv *priv)
{
#ifdef CONFIG_PM_SLEEP
priv->ctx = devm_kzalloc(priv->dev, sizeof (*priv->ctx), GFP_KERNEL);
if (!priv->ctx)
return -ENOMEM;
#endif
/* Set default configuration */
th1520_mbox_write(priv, 0 xff, TH_1520_MBOX_CLR);
th1520_mbox_write(priv, 0 x0, TH_1520_MBOX_MASK);
return 0 ;
}
static struct mbox_chan *th1520_mbox_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *sp)
{
u32 chan;
if (sp->args_count != 1 ) {
dev_err(mbox->dev, "Invalid argument count %d\n" ,
sp->args_count);
return ERR_PTR(-EINVAL);
}
chan = sp->args[0 ]; /* comm remote channel */
if (chan >= mbox->num_chans) {
dev_err(mbox->dev, "Not supported channel number: %d\n" , chan);
return ERR_PTR(-EINVAL);
}
if (chan == TH_1520_MBOX_ICU_KERNEL_CPU0) {
dev_err(mbox->dev, "Cannot communicate with yourself\n" );
return ERR_PTR(-EINVAL);
}
return &mbox->chans[chan];
}
static void __iomem *th1520_map_mmio(struct platform_device *pdev,
char *res_name, size_t offset)
{
void __iomem *mapped;
struct resource *res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, res_name);
if (!res) {
dev_err(&pdev->dev, "Failed to get resource: %s\n" , res_name);
return ERR_PTR(-EINVAL);
}
mapped = devm_ioremap(&pdev->dev, res->start + offset,
resource_size(res) - offset);
if (!mapped) {
dev_err(&pdev->dev, "Failed to map resource: %s\n" , res_name);
return ERR_PTR(-ENOMEM);
}
return mapped;
}
static void th1520_disable_clk(void *data)
{
struct th1520_mbox_priv *priv = data;
clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks);
}
static int th1520_mbox_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct th1520_mbox_priv *priv;
unsigned int remote_idx = 0 ;
unsigned int i;
int ret;
priv = devm_kzalloc(dev, sizeof (*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->clocks[0 ].id = "clk-local" ;
priv->clocks[1 ].id = "clk-remote-icu0" ;
priv->clocks[2 ].id = "clk-remote-icu1" ;
priv->clocks[3 ].id = "clk-remote-icu2" ;
ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks),
priv->clocks);
if (ret) {
dev_err(dev, "Failed to get clocks\n" );
return ret;
}
ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks);
if (ret) {
dev_err(dev, "Failed to enable clocks\n" );
return ret;
}
ret = devm_add_action_or_reset(dev, th1520_disable_clk, priv);
if (ret) {
clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks);
return ret;
}
/*
* The address mappings in the device tree align precisely with those
* outlined in the manual . However , register offsets within these
* mapped regions are irregular , particularly for remote - icu0 .
* Consequently , th1520_map_mmio ( ) requires an additional parameter to
* handle this quirk .
*/
priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0] =
th1520_map_mmio(pdev, "local" , 0 x0);
if (IS_ERR(priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0]))
return PTR_ERR(priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0]);
priv->remote_icu[0 ] = th1520_map_mmio(pdev, "remote-icu0" , 0 x4000);
if (IS_ERR(priv->remote_icu[0 ]))
return PTR_ERR(priv->remote_icu[0 ]);
priv->remote_icu[1 ] = th1520_map_mmio(pdev, "remote-icu1" , 0 x0);
if (IS_ERR(priv->remote_icu[1 ]))
return PTR_ERR(priv->remote_icu[1 ]);
priv->remote_icu[2 ] = th1520_map_mmio(pdev, "remote-icu2" , 0 x0);
if (IS_ERR(priv->remote_icu[2 ]))
return PTR_ERR(priv->remote_icu[2 ]);
priv->local_icu[TH_1520_MBOX_ICU_CPU1] =
priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0] +
TH_1520_MBOX_CHAN_RES_SIZE;
priv->local_icu[TH_1520_MBOX_ICU_CPU2] =
priv->local_icu[TH_1520_MBOX_ICU_CPU1] +
TH_1520_MBOX_CHAN_RES_SIZE;
priv->local_icu[TH_1520_MBOX_ICU_CPU3] =
priv->local_icu[TH_1520_MBOX_ICU_CPU2] +
TH_1520_MBOX_CHAN_RES_SIZE;
priv->cur_cpu_ch_base = priv->local_icu[TH_1520_MBOX_ICU_KERNEL_CPU0];
priv->irq = platform_get_irq(pdev, 0 );
if (priv->irq < 0 )
return priv->irq;
/* init the chans */
for (i = 0 ; i < TH_1520_MBOX_CHANS; i++) {
struct th1520_mbox_con_priv *cp = &priv->con_priv[i];
cp->idx = i;
cp->chan = &priv->mbox_chans[i];
priv->mbox_chans[i].con_priv = cp;
snprintf(cp->irq_desc, sizeof (cp->irq_desc),
"th1520_mbox_chan[%i]" , cp->idx);
cp->comm_local_base = priv->local_icu[i];
if (i != TH_1520_MBOX_ICU_KERNEL_CPU0) {
cp->comm_remote_base = priv->remote_icu[remote_idx];
remote_idx++;
}
}
spin_lock_init(&priv->mbox_lock);
priv->mbox.dev = dev;
priv->mbox.ops = &th1520_mbox_ops;
priv->mbox.chans = priv->mbox_chans;
priv->mbox.num_chans = TH_1520_MBOX_CHANS;
priv->mbox.of_xlate = th1520_mbox_xlate;
priv->mbox.txdone_irq = true ;
platform_set_drvdata(pdev, priv);
ret = th1520_mbox_init_generic(priv);
if (ret) {
dev_err(dev, "Failed to init mailbox context\n" );
return ret;
}
return devm_mbox_controller_register(dev, &priv->mbox);
}
static const struct of_device_id th1520_mbox_dt_ids[] = {
{ .compatible = "thead,th1520-mbox" },
{}
};
MODULE_DEVICE_TABLE(of, th1520_mbox_dt_ids);
#ifdef CONFIG_PM_SLEEP
static int __maybe_unused th1520_mbox_suspend_noirq(struct device *dev)
{
struct th1520_mbox_priv *priv = dev_get_drvdata(dev);
struct th1520_mbox_context *ctx = priv->ctx;
u32 i;
/*
* ONLY interrupt mask bit should be stored and restores .
* INFO data all assumed to be lost .
*/
for (i = 0 ; i < TH_1520_MBOX_CHANS; i++) {
ctx->intr_mask[i] =
ioread32(priv->local_icu[i] + TH_1520_MBOX_MASK);
}
return 0 ;
}
static int __maybe_unused th1520_mbox_resume_noirq(struct device *dev)
{
struct th1520_mbox_priv *priv = dev_get_drvdata(dev);
struct th1520_mbox_context *ctx = priv->ctx;
u32 i;
for (i = 0 ; i < TH_1520_MBOX_CHANS; i++) {
iowrite32(ctx->intr_mask[i],
priv->local_icu[i] + TH_1520_MBOX_MASK);
}
return 0 ;
}
#endif
static int __maybe_unused th1520_mbox_runtime_suspend(struct device *dev)
{
struct th1520_mbox_priv *priv = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks);
return 0 ;
}
static int __maybe_unused th1520_mbox_runtime_resume(struct device *dev)
{
struct th1520_mbox_priv *priv = dev_get_drvdata(dev);
int ret;
ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks);
if (ret)
dev_err(dev, "Failed to enable clocks in runtime resume\n" );
return ret;
}
static const struct dev_pm_ops th1520_mbox_pm_ops = {
#ifdef CONFIG_PM_SLEEP
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(th1520_mbox_suspend_noirq,
th1520_mbox_resume_noirq)
#endif
SET_RUNTIME_PM_OPS(th1520_mbox_runtime_suspend,
th1520_mbox_runtime_resume, NULL)
};
static struct platform_driver th1520_mbox_driver = {
.probe = th1520_mbox_probe,
.driver = {
.name = "th1520-mbox" ,
.of_match_table = th1520_mbox_dt_ids,
.pm = &th1520_mbox_pm_ops,
},
};
module_platform_driver(th1520_mbox_driver);
MODULE_DESCRIPTION("Thead TH-1520 mailbox IPC driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=95 H=96 G=95
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland