1026 lines
32 KiB
C
1026 lines
32 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
|
|
/*
|
|
* Allwinner msgbox driver for Linux.
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
//#define DEBUG
|
|
#include <sunxi-common.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/err.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/mailbox_controller.h>
|
|
|
|
#define SUNXI_MSGBOX_OFFSET(n) (0x100 * (n))
|
|
#define SUNXI_MSGBOX_READ_IRQ_ENABLE(n) (0x20 + SUNXI_MSGBOX_OFFSET(n))
|
|
#define SUNXI_MSGBOX_READ_IRQ_STATUS(n) (0x24 + SUNXI_MSGBOX_OFFSET(n))
|
|
#define SUNXI_MSGBOX_WRITE_IRQ_ENABLE(n) (0x30 + SUNXI_MSGBOX_OFFSET(n))
|
|
#define SUNXI_MSGBOX_WRITE_IRQ_STATUS(n) (0x34 + SUNXI_MSGBOX_OFFSET(n))
|
|
#define SUNXI_MSGBOX_DEBUG_REGISTER(n) (0x40 + SUNXI_MSGBOX_OFFSET(n))
|
|
#define SUNXI_MSGBOX_FIFO_STATUS(n, p) (0x50 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
|
|
#define SUNXI_MSGBOX_MSG_STATUS(n, p) (0x60 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
|
|
#define SUNXI_MSGBOX_MSG_FIFO(n, p) (0x70 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
|
|
#define SUNXI_MSGBOX_WRITE_IRQ_THRESHOLD(n, p) (0x80 + SUNXI_MSGBOX_OFFSET(n) + 0x4 * (p))
|
|
|
|
/* SUNXI_MSGBOX_READ_IRQ_ENABLE */
|
|
#define RD_IRQ_EN_MASK 0x1
|
|
#define RD_IRQ_EN_SHIFT(p) ((p) * 2)
|
|
|
|
/* SUNXI_MSGBOX_READ_IRQ_STATUS */
|
|
#define RD_IRQ_PEND_MASK 0x1
|
|
#define RD_IRQ_PEND_SHIFT(p) ((p) * 2)
|
|
|
|
/* SUNXI_MSGBOX_WRITE_IRQ_ENABLE */
|
|
#define WR_IRQ_EN_MASK 0x1
|
|
#define WR_IRQ_EN_SHIFT(p) ((p) * 2 + 1)
|
|
|
|
/* SUNXI_MSGBOX_WRITE_IRQ_STATUS */
|
|
#define WR_IRQ_PEND_MASK 0x1
|
|
#define WR_IRQ_PEND_SHIFT(p) ((p) * 2 + 1)
|
|
|
|
/* SUNXI_MSGBOX_MSG_STATUS */
|
|
#define MSG_NUM_MASK 0xF
|
|
#define MSG_NUM_SHIFT 0
|
|
|
|
/* SUNXI_MSGBOX_WRITE_IRQ_THRESHOLD */
|
|
#define WR_IRQ_THR_MASK 0x3
|
|
#define WR_IRQ_THR_SHIFT 0
|
|
|
|
/*
|
|
* AW msgbox hardware data information
|
|
* Each msgox can be used for RX by current processor, it can trigger
|
|
* remote processor interrupt to notify remote processor and can receive
|
|
* interrupt if has incoming message.
|
|
*
|
|
* @processors_max: The number of process that have msgbox controller in this SOC
|
|
* @channels_max: The number of channels in every msgbox controller
|
|
* @fifo_msg_max: The depth of FIFO
|
|
* @mbox_num_chans: All the num of channels in this SOC, mbox_num_chans = (processors_max - 1) * channels_max
|
|
* @to_coef_n: The coef N, N = to_coef_n[local_id][remote_id]
|
|
* @to_remote_id: The remote process id for current process, remote_id = to_coef_n[local_id][coef_n]
|
|
*/
|
|
struct sunxi_msgbox_hwdata {
|
|
int processors_max;
|
|
int channels_max;
|
|
int fifo_msg_max;
|
|
int mbox_num_chans;
|
|
int to_coef_n[4][4];
|
|
int to_remote_id[4][4];
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
/* Registers which needs to be saved and restored before and after sleeping */
|
|
static u32 sunxi_msgbox_regs_offset[] = {
|
|
SUNXI_MSGBOX_READ_IRQ_ENABLE(0),
|
|
SUNXI_MSGBOX_WRITE_IRQ_ENABLE(0),
|
|
SUNXI_MSGBOX_READ_IRQ_ENABLE(1),
|
|
SUNXI_MSGBOX_WRITE_IRQ_ENABLE(1),
|
|
SUNXI_MSGBOX_READ_IRQ_ENABLE(2),
|
|
SUNXI_MSGBOX_WRITE_IRQ_ENABLE(2),
|
|
};
|
|
#endif /* CONFIG_PM */
|
|
|
|
/**
|
|
* AW msgbox controller data
|
|
*
|
|
* msbox controller support allocate channel for message transferring.
|
|
*
|
|
* @controller: Representation of a communication channel controller
|
|
* @hwdata: Msgbox hardware data
|
|
* @pdev: Platform device
|
|
* @dev: Device to which it is attached
|
|
* fllowing all of dts data
|
|
* @res: Base address of the register mapping region
|
|
* @clk: The msgbox working clk
|
|
* @reset: The msgbox reset clk
|
|
* @irq: The msgbox irq num for all the msgbox controller in this SOC
|
|
* @irq_cnt: The msgbox irq num, irq_cnt should equal to hwdata->processors_max
|
|
* @local_id: Curren process id num for all msgbox controller
|
|
* @regs_backup Save msgbox status register values during sleep
|
|
* @base_addr: Base address of the register mapping region
|
|
*/
|
|
struct sunxi_msgbox {
|
|
struct mbox_controller controller;
|
|
struct sunxi_msgbox_hwdata *hwdata;
|
|
struct platform_device *pdev;
|
|
struct device *dev;
|
|
struct resource *res;
|
|
struct clk *clk;
|
|
struct reset_control *reset;
|
|
int *irq;
|
|
int irq_cnt;
|
|
int local_id;
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
u32 regs_backup[ARRAY_SIZE(sunxi_msgbox_regs_offset)];
|
|
#endif /* CONFIG_PM */
|
|
void __iomem *base_addr[0];
|
|
};
|
|
|
|
static bool sunxi_msgbox_peek_data(struct mbox_chan *chan);
|
|
|
|
static void reg_val_update(void __iomem *reg, u32 mask, u32 shift, u32 val)
|
|
{
|
|
u32 reg_val;
|
|
|
|
reg_val = readl(reg);
|
|
reg_val &= ~(mask << shift);
|
|
reg_val |= ((val & mask) << shift);
|
|
writel(reg_val, reg);
|
|
}
|
|
|
|
/*
|
|
* For local processor, mailbox channel index is defined from coefficient N(local_n remote_n)
|
|
* and coefficient P(the used channel num). eg:
|
|
* arm to dsp
|
|
* - local_id: 0
|
|
* - local_n: 1(DSP->ARM)
|
|
* - remote_id: 1
|
|
* - remote_n: 0(ARM->DSP)
|
|
*
|
|
* mailbox channel index(in dts:<&msgbox x>) = local_n * SUNXI_MSGBOX_CHANNELS_MAX + P
|
|
*/
|
|
static inline void mbox_chan_id_to_coef_n_p(struct sunxi_msgbox *chip, int mbox_chan_id, int *local_n, int *coef_p)
|
|
{
|
|
*local_n = mbox_chan_id / chip->hwdata->channels_max;
|
|
*coef_p = mbox_chan_id % chip->hwdata->channels_max;
|
|
}
|
|
|
|
static inline void mbox_chan_to_coef_n_p(struct sunxi_msgbox *chip, struct mbox_chan *chan, int *coef_n, int *coef_p)
|
|
{
|
|
/* chan is an element in the array chans[] --> assignment in the of_xlate */
|
|
mbox_chan_id_to_coef_n_p(chip, chan - chan->mbox->chans, coef_n, coef_p);
|
|
}
|
|
|
|
/*
|
|
* when sender_id is local_id and receiver_id is remote_id, return local_n
|
|
* when sender_id is remote_id and receiver_id is local_id, return remote_n
|
|
* */
|
|
static inline int sunxi_msgbox_coef_n(struct sunxi_msgbox *chip, int sender_id, int receiver_id)
|
|
{
|
|
BUG_ON(chip->hwdata->to_coef_n[sender_id][receiver_id] == -1);
|
|
return chip->hwdata->to_coef_n[sender_id][receiver_id];
|
|
}
|
|
|
|
static inline int sunxi_msgbox_remote_id(struct sunxi_msgbox *chip, int local_id, int coef_n)
|
|
{
|
|
BUG_ON(chip->hwdata->to_remote_id[local_id][coef_n] == -1);
|
|
return chip->hwdata->to_remote_id[local_id][coef_n];
|
|
}
|
|
|
|
static void *sunxi_msgbox_reg_base(struct sunxi_msgbox *chip, int index)
|
|
{
|
|
void *base;
|
|
|
|
BUG_ON(index >= chip->hwdata->processors_max);
|
|
base = chip->base_addr[index];
|
|
BUG_ON(!base);
|
|
|
|
return base;
|
|
}
|
|
|
|
static inline struct sunxi_msgbox *to_sunxi_msgbox(struct mbox_chan *chan)
|
|
{
|
|
return chan->con_priv;
|
|
}
|
|
|
|
/* set the msgbox's fifo trigger level */
|
|
static void sunxi_msgbox_set_write_irq_threshold(struct sunxi_msgbox *chip,
|
|
void __iomem *base, int n, int p,
|
|
int threshold)
|
|
{
|
|
u32 thr_val;
|
|
void __iomem *reg = base + SUNXI_MSGBOX_WRITE_IRQ_THRESHOLD(n, p);
|
|
|
|
switch (threshold) {
|
|
case 8:
|
|
thr_val = 3;
|
|
break;
|
|
case 4:
|
|
thr_val = 2;
|
|
break;
|
|
case 2:
|
|
thr_val = 1;
|
|
break;
|
|
case 1:
|
|
thr_val = 0;
|
|
break;
|
|
default:
|
|
dev_warn(chip->dev, "Invalid write irq threshold (%d). Use 1 instead\n", threshold);
|
|
thr_val = 0;
|
|
break;
|
|
}
|
|
|
|
reg_val_update(reg, WR_IRQ_THR_MASK, WR_IRQ_THR_SHIFT, thr_val);
|
|
}
|
|
|
|
static void sunxi_msgbox_read_handler(struct sunxi_msgbox *chip, struct mbox_chan *chan,
|
|
void __iomem *base, int local_n, int p)
|
|
{
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(10);
|
|
u32 msg;
|
|
int ret = -1;
|
|
|
|
while (sunxi_msgbox_peek_data(chan) && time_before(jiffies, timeout)) {
|
|
msg = readl(base + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
|
|
dev_dbg(chip->dev, "process-%d read data [0x%x] by channel %d from processor-%d success\n",
|
|
chip->local_id, msg, p, sunxi_msgbox_remote_id(chip, chip->local_id, local_n));
|
|
mbox_chan_received_data(chan, &msg);
|
|
ret = 0;
|
|
}
|
|
if (ret)
|
|
dev_err(chip->dev, "read data timeout\n");
|
|
|
|
/* The IRQ pending can be cleared only once the FIFO is empty. */
|
|
set_bits(base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n), RD_IRQ_PEND_MASK << RD_IRQ_PEND_SHIFT(p));
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
static void sunxi_msgbox_write_handler(struct sunxi_msgbox *chip, struct mbox_chan *chan,
|
|
void __iomem *base, int remote_n, int p)
|
|
{
|
|
/*
|
|
* In msgbox hardware, the write IRQ will be triggered if the empty
|
|
* level in FIFO reaches the write IRQ threshold. It means that there
|
|
* is empty space in FIFO for local processor to write. Here we use
|
|
* the write IRQ to indicate TX is done, to ensure that there is empty
|
|
* space in FIFO for next message to send.
|
|
*/
|
|
|
|
/* Disable write IRQ */
|
|
clear_field(base + SUNXI_MSGBOX_WRITE_IRQ_ENABLE(remote_n), WR_IRQ_EN_MASK << WR_IRQ_EN_SHIFT(p));
|
|
|
|
mbox_chan_txdone(chan, 0);
|
|
|
|
dev_dbg(chip->dev, "process-%d write data by channel %d to processor-%d done\n", remote_n, p, chip->local_id);
|
|
|
|
/* Clear write IRQ pending */
|
|
set_bits(base + SUNXI_MSGBOX_WRITE_IRQ_STATUS(remote_n), WR_IRQ_PEND_MASK << WR_IRQ_PEND_SHIFT(p));
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t sunxi_msgbox_handler(int irq, void *dev_id)
|
|
{
|
|
struct sunxi_msgbox *chip = dev_id;
|
|
struct mbox_chan *chan;
|
|
int local_id, local_n, remote_id, remote_n, p;
|
|
void __iomem *read_reg_base;
|
|
void __iomem *write_reg_base;
|
|
u32 read_irq_en, read_irq_pending;
|
|
u32 write_irq_en, write_irq_pending;
|
|
int i;
|
|
|
|
for (i = 0; i < chip->hwdata->mbox_num_chans; i++) {
|
|
chan = &chip->controller.chans[i];
|
|
|
|
mbox_chan_id_to_coef_n_p(chip, i, &local_n, &p);
|
|
local_id = chip->local_id;
|
|
remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
|
|
read_reg_base = sunxi_msgbox_reg_base(chip, local_id);
|
|
write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
|
|
read_irq_en = get_field(
|
|
read_reg_base + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n),
|
|
RD_IRQ_EN_MASK << RD_IRQ_EN_SHIFT(p));
|
|
read_irq_pending = get_field(
|
|
read_reg_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n),
|
|
RD_IRQ_PEND_MASK << RD_IRQ_PEND_SHIFT(p));
|
|
write_irq_en = get_field(
|
|
write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_ENABLE(remote_n),
|
|
WR_IRQ_EN_MASK << WR_IRQ_EN_SHIFT(p));
|
|
write_irq_pending = get_field(
|
|
write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_STATUS(remote_n),
|
|
WR_IRQ_PEND_MASK << WR_IRQ_PEND_SHIFT(p));
|
|
|
|
if (read_irq_en && read_irq_pending)
|
|
sunxi_msgbox_read_handler(chip, chan, read_reg_base, local_n, p);
|
|
|
|
#if IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
if (write_irq_en && write_irq_pending)
|
|
sunxi_msgbox_write_handler(chip, chan, write_reg_base, remote_n, p);
|
|
#endif
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sunxi_msgbox_startup(struct mbox_chan *chan)
|
|
{
|
|
struct sunxi_msgbox *chip = to_sunxi_msgbox(chan);
|
|
int local_id, remote_id, local_n, remote_n, p;
|
|
void __iomem *read_reg_base;
|
|
void __iomem *write_reg_base;
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(10);
|
|
|
|
mbox_chan_to_coef_n_p(chip, chan, &local_n, &p);
|
|
local_id = chip->local_id;
|
|
remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
read_reg_base = sunxi_msgbox_reg_base(chip, local_id);
|
|
write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
|
|
/* Flush read FIFO before transfer */
|
|
while (sunxi_msgbox_peek_data(chan) && time_before(jiffies, timeout))
|
|
readl(read_reg_base + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
|
|
|
|
/* Clear read IRQ pending before transfer */
|
|
set_bits(read_reg_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n), RD_IRQ_PEND_MASK << RD_IRQ_PEND_SHIFT(p));
|
|
|
|
/* Enable read IRQ */
|
|
set_bits(read_reg_base + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n), RD_IRQ_EN_MASK << RD_IRQ_EN_SHIFT(p));
|
|
|
|
/* Clear remote process's write IRQ pending */
|
|
set_bits(write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_STATUS(remote_n), WR_IRQ_PEND_MASK << WR_IRQ_PEND_SHIFT(p));
|
|
|
|
#if IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
/*
|
|
* Enable write IRQ after writing message to FIFO
|
|
* Because we use the write IRQ to indicate whether FIFO has empty space for "next message"
|
|
* rather than "this message" to send.
|
|
*/
|
|
set_bits(write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_ENABLE(remote_n), WR_IRQ_EN_MASK << WR_IRQ_EN_SHIFT(p));
|
|
#endif
|
|
|
|
/*
|
|
* Configure the FIFO empty level to trigger the write IRQ to 1.
|
|
* It means that if the write IRQ is enabled, once the FIFO is not full,
|
|
* the write IRQ will be triggered.
|
|
*/
|
|
sunxi_msgbox_set_write_irq_threshold(chip, write_reg_base, remote_n, p, 1);
|
|
|
|
dev_dbg(chip->dev, "process-%d --> proccss-%d by channel %d startup complete\n",
|
|
local_id, remote_id, p);
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_msgbox_send_data(struct mbox_chan *chan, void *data)
|
|
{
|
|
struct sunxi_msgbox *chip = to_sunxi_msgbox(chan);
|
|
int local_id = chip->local_id;
|
|
int local_n, remote_id, remote_n, p;
|
|
u32 remaining_space_in_fifo, msg;
|
|
void __iomem *write_reg_base; /* the base addr of the msgbox controller that you want to send */
|
|
|
|
/*
|
|
* Here we consider the data is always a pointer to u32.
|
|
* Should we define a data structure, e.g. 'struct sunxi_mailbox_message',
|
|
* to hide the actual data type for mailbox client users?
|
|
*/
|
|
msg = *(u32 *)data;
|
|
|
|
mbox_chan_to_coef_n_p(chip, chan, &local_n, &p);
|
|
remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
|
|
/*
|
|
* Check whether FIFO is full.
|
|
*
|
|
* Ordinarily the 'tx_done' of previous message ensures the FIFO has
|
|
* empty space for this message to send. But in case the FIFO is already
|
|
* full before sending the first message, we check the number of messages
|
|
* in FIFO anyway.
|
|
*/
|
|
remaining_space_in_fifo = chip->hwdata->fifo_msg_max - get_field(write_reg_base + SUNXI_MSGBOX_MSG_STATUS(remote_n, p), MSG_NUM_MASK);
|
|
if (remaining_space_in_fifo <= 0) {
|
|
dev_err(chip->dev, "Channel %d to processor %d: FIFO is full\n", p, remote_id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Write message to remote process's msgbox controller's FIFO */
|
|
writel(msg, write_reg_base + SUNXI_MSGBOX_MSG_FIFO(remote_n, p));
|
|
|
|
dev_dbg(chip->dev, "processor-%d use channel %d send data [0x%x] to processor-%d success\n",
|
|
local_id, p, msg, remote_id);
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_msgbox_shutdown(struct mbox_chan *chan)
|
|
{
|
|
struct sunxi_msgbox *chip = to_sunxi_msgbox(chan);
|
|
int local_id, remote_id;
|
|
int local_n, remote_n, p;
|
|
void __iomem *read_reg_base;
|
|
void __iomem *write_reg_base;
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(10);
|
|
|
|
mbox_chan_to_coef_n_p(chip, chan, &local_n, &p);
|
|
local_id = chip->local_id;
|
|
remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
read_reg_base = sunxi_msgbox_reg_base(chip, local_id);
|
|
write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
|
|
/* Disable the write IRQ */
|
|
clear_field(write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_ENABLE(remote_n), WR_IRQ_EN_MASK << WR_IRQ_EN_SHIFT(p));
|
|
/* Clear write IRQ pending */
|
|
set_bits(write_reg_base + SUNXI_MSGBOX_WRITE_IRQ_STATUS(remote_n), WR_IRQ_PEND_MASK << WR_IRQ_PEND_SHIFT(p));
|
|
|
|
/* Attempt to flush the receive FIFO until the IRQ is cleared. */
|
|
do {
|
|
while (sunxi_msgbox_peek_data(chan) && time_before(jiffies, timeout))
|
|
readl(read_reg_base + SUNXI_MSGBOX_MSG_FIFO(local_n, p));
|
|
/* Disable the read IRQ */
|
|
clear_field(read_reg_base + SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n),
|
|
RD_IRQ_EN_MASK << RD_IRQ_EN_SHIFT(p));
|
|
/* Clear the read IRQ pending */
|
|
set_bits(read_reg_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n),
|
|
RD_IRQ_PEND_MASK << RD_IRQ_PEND_SHIFT(p));
|
|
} while (get_field(read_reg_base + SUNXI_MSGBOX_READ_IRQ_STATUS(local_n),
|
|
RD_IRQ_PEND_MASK << RD_IRQ_PEND_SHIFT(p)) && time_before(jiffies, timeout));
|
|
|
|
dev_dbg(chip->dev, "process-%d --> process-%d by channel %d shutdown complete\n", local_id, remote_id, p);
|
|
}
|
|
|
|
#if !IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
static bool sunxi_msgbox_last_tx_done(struct mbox_chan *chan)
|
|
{
|
|
/*
|
|
* Here we consider a transmission is done if the FIFO is not full.
|
|
* This ensures that the next message can be written to FIFO, and local
|
|
* processor need not to wait until remote processor has read this
|
|
* message from FIFO.
|
|
*/
|
|
struct sunxi_msgbox *chip = to_sunxi_msgbox(chan);
|
|
int local_id = chip->local_id;
|
|
int local_n, remote_id, remote_n, p;
|
|
void __iomem *write_reg_base;
|
|
void __iomem *status_reg;
|
|
u32 msg_num;
|
|
|
|
mbox_chan_to_coef_n_p(chip, chan, &local_n, &p);
|
|
remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
|
|
|
|
write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
status_reg = write_reg_base + SUNXI_MSGBOX_MSG_STATUS(remote_n, p);
|
|
msg_num = get_field(status_reg, MSG_NUM_MASK);
|
|
|
|
return msg_num < chip->hwdata->fifo_msg_max ? true : false;
|
|
}
|
|
#endif
|
|
|
|
static bool sunxi_msgbox_peek_data(struct mbox_chan *chan)
|
|
{
|
|
struct sunxi_msgbox *chip = to_sunxi_msgbox(chan);
|
|
int local_n, p;
|
|
u32 msg_num;
|
|
void __iomem *status_reg;
|
|
|
|
mbox_chan_to_coef_n_p(chip, chan, &local_n, &p);
|
|
status_reg = sunxi_msgbox_reg_base(chip, chip->local_id) + SUNXI_MSGBOX_MSG_STATUS(local_n, p);
|
|
msg_num = get_field(status_reg, MSG_NUM_MASK);
|
|
|
|
return !!msg_num;
|
|
}
|
|
|
|
/*
|
|
* Each maibox channel is bidirectional and can send and receive data
|
|
* when local process read from remote process use the channel of local process msgbox ctroller
|
|
* when local process write data to remote process use the channel of remote process msgbox ctroller
|
|
* */
|
|
static struct mbox_chan *sunxi_mbox_xlate(struct mbox_controller *mbox, const struct of_phandle_args *sp)
|
|
{
|
|
int ind = sp->args[0];
|
|
if (ind >= mbox->num_chans)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return &mbox->chans[ind];
|
|
}
|
|
|
|
static const struct mbox_chan_ops sunxi_msgbox_chan_ops = {
|
|
.startup = sunxi_msgbox_startup,
|
|
.send_data = sunxi_msgbox_send_data,
|
|
.shutdown = sunxi_msgbox_shutdown,
|
|
#if IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
.last_tx_done = NULL,
|
|
#else
|
|
.last_tx_done = sunxi_msgbox_last_tx_done,
|
|
#endif
|
|
.peek_data = sunxi_msgbox_peek_data,
|
|
};
|
|
|
|
/* Note:
|
|
* When support new platform, msgbox driver maintainers need to
|
|
* add coefficients N, remote_id and local_id table according to
|
|
* spec.
|
|
*
|
|
* The '-1' in the table means Allwinnertech msgbox not supporting.
|
|
*
|
|
* In sunxi msgbox, the coefficient N represents "the index of other processor
|
|
* that communicates with local processor". For one specific local processor,
|
|
* different coefficient N means differnt remote processor.
|
|
*
|
|
* sun8iw20:
|
|
* ARM:0,DSP:1,CPUS:2
|
|
* --------------------------------------------
|
|
* local_id N remote_id
|
|
* --------------------------------------------
|
|
* 0 0 1
|
|
* 0 1 2
|
|
* --------------------------------------------
|
|
* 1 0 0
|
|
* 1 1 2
|
|
* --------------------------------------------
|
|
* 2 0 0
|
|
* 2 1 1
|
|
* --------------------------------------------
|
|
*/
|
|
static const struct sunxi_msgbox_hwdata sun8iw20_hwdata = {
|
|
.processors_max = 3,
|
|
.channels_max = 4,
|
|
.fifo_msg_max = 8,
|
|
.mbox_num_chans = 8,
|
|
.to_coef_n = {
|
|
{-1, 0, 1, -1},
|
|
{0, -1, 1, -1},
|
|
{0, 1, -1, -1},
|
|
{-1, -1, -1, -1}
|
|
},
|
|
.to_remote_id = {
|
|
{1, 2, -1, -1},
|
|
{0, 2, -1, -1},
|
|
{0, 1, -1, -1},
|
|
{-1, -1, -1, -1}
|
|
}
|
|
};
|
|
|
|
/* sun55iw3:
|
|
* ARM:0,DSP:1,CPUS:2,RV:3
|
|
* --------------------------------------------
|
|
* local_id N remote_id
|
|
* --------------------------------------------
|
|
* 0 1 1
|
|
* 0 0 2
|
|
* 0 2 3
|
|
* --------------------------------------------
|
|
* 1 0 0
|
|
* 1 1 2
|
|
* 1 2 3
|
|
* --------------------------------------------
|
|
* 2 0 0
|
|
* 2 1 1
|
|
* 2 2 3
|
|
* --------------------------------------------
|
|
* 3 2 0
|
|
* 3 1 1
|
|
* 3 0 2
|
|
* --------------------------------------------
|
|
*/
|
|
static const struct sunxi_msgbox_hwdata sun55iw3_hwdata = {
|
|
.processors_max = 4,
|
|
.channels_max = 4,
|
|
.fifo_msg_max = 8,
|
|
.mbox_num_chans = 12,
|
|
.to_coef_n = {
|
|
{-1, 1, 0, 2},
|
|
{0, -1, 1, 2},
|
|
{0, 1, -1, 2},
|
|
{2, 1, 0, -1}
|
|
},
|
|
.to_remote_id = {
|
|
{2, 1, 3, -1},
|
|
{0, 2, 3, -1},
|
|
{0, 1, 3, -1},
|
|
{2, 1, 0, -1},
|
|
}
|
|
};
|
|
|
|
/* sun55iw6:
|
|
* ARM:0,CPUS:1,RV:2
|
|
* --------------------------------------------
|
|
* local_id N remote_id
|
|
* --------------------------------------------
|
|
* 0 0 1
|
|
* 0 1 2
|
|
* --------------------------------------------
|
|
* 1 0 0
|
|
* 1 1 2
|
|
* --------------------------------------------
|
|
* 2 0 0
|
|
* 2 1 1
|
|
* --------------------------------------------
|
|
*/
|
|
static const struct sunxi_msgbox_hwdata sun55iw6_hwdata = {
|
|
.processors_max = 3,
|
|
.channels_max = 4,
|
|
.fifo_msg_max = 8,
|
|
.mbox_num_chans = 8,
|
|
.to_coef_n = {
|
|
{-1, 0, 1, -1},
|
|
{0, -1, 1, -1},
|
|
{0, 1, -1, -1},
|
|
{-1, -1, -1, -1}
|
|
},
|
|
.to_remote_id = {
|
|
{1, 2, -1, -1},
|
|
{0, 2, -1, -1},
|
|
{0, 1, -1, -1},
|
|
{-1, -1, -1, -1},
|
|
}
|
|
};
|
|
|
|
/* sun60iw1:
|
|
* ARM:0,CPUS:1,DSP:2,RV:3
|
|
* --------------------------------------------
|
|
* local_id N remote_id
|
|
* --------------------------------------------
|
|
* 0 0 1
|
|
* 0 1 2
|
|
* 0 2 3
|
|
* --------------------------------------------
|
|
* 1 0 0
|
|
* 1 1 2
|
|
* 1 2 3
|
|
* --------------------------------------------
|
|
* 2 0 0
|
|
* 2 1 1
|
|
* 2 2 3
|
|
* --------------------------------------------
|
|
* 3 0 0
|
|
* 3 1 1
|
|
* 3 2 2
|
|
* --------------------------------------------
|
|
*/
|
|
static const struct sunxi_msgbox_hwdata sun60iw1_hwdata = {
|
|
.processors_max = 4,
|
|
.channels_max = 4,
|
|
.fifo_msg_max = 8,
|
|
.mbox_num_chans = 12,
|
|
.to_coef_n = {
|
|
{-1, 0, 1, 2},
|
|
{0, -1, 1, 2},
|
|
{0, 1, -1, 2},
|
|
{0, 1, 2, -1}
|
|
},
|
|
.to_remote_id = {
|
|
{1, 2, 3, -1},
|
|
{0, 2, 3, -1},
|
|
{0, 1, 3, -1},
|
|
{0, 1, 2, -1},
|
|
}
|
|
};
|
|
|
|
/* sun55iw5:
|
|
* ARM:0,CPUS:1
|
|
* --------------------------------------------
|
|
* local_id N remote_id
|
|
* --------------------------------------------
|
|
* 0 0 1
|
|
* --------------------------------------------
|
|
* 1 0 0
|
|
* --------------------------------------------
|
|
*/
|
|
static const struct sunxi_msgbox_hwdata sun55iw5_hwdata = {
|
|
.processors_max = 2,
|
|
.channels_max = 4,
|
|
.fifo_msg_max = 8,
|
|
.mbox_num_chans = 4,
|
|
.to_coef_n = {
|
|
{-1, 0, -1, -1},
|
|
{0, -1, -1, -1},
|
|
{-1, -1, -1, -1},
|
|
{-1, -1, -1, -1}
|
|
},
|
|
.to_remote_id = {
|
|
{1, -1, -1, -1},
|
|
{0, -1, -1, -1},
|
|
{-1, -1, -1, -1},
|
|
{-1, -1, -1, -1},
|
|
}
|
|
};
|
|
|
|
static const struct of_device_id sunxi_msgbox_of_match[] = {
|
|
{ .compatible = "allwinner,sun8iw20-msgbox", .data = &sun8iw20_hwdata},
|
|
{ .compatible = "allwinner,sun55iw3-msgbox", .data = &sun55iw3_hwdata},
|
|
{ .compatible = "allwinner,sun55iw6-msgbox", .data = &sun55iw6_hwdata},
|
|
{ .compatible = "allwinner,sun60iw1-msgbox", .data = &sun60iw1_hwdata},
|
|
/* The msgbox resources of sun60iw2 and sun55iw5 are the same, so use sun55iw5_hwdata uniformly */
|
|
{ .compatible = "allwinner,sun55iw5-msgbox", .data = &sun55iw5_hwdata},
|
|
{ .compatible = "allwinner,sun60iw2-msgbox", .data = &sun55iw5_hwdata},
|
|
{ .compatible = "allwinner,sun8iw21-msgbox", .data = &sun55iw5_hwdata},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sunxi_msgbox_of_match);
|
|
|
|
static void sunxi_msgbox_disable_irq(struct sunxi_msgbox *chip)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < chip->hwdata->processors_max - 1; i++) {
|
|
int local_n = i;
|
|
int local_id = chip->local_id;
|
|
int remote_id = sunxi_msgbox_remote_id(chip, local_id, local_n);
|
|
int remote_n = sunxi_msgbox_coef_n(chip, remote_id, local_id);
|
|
void __iomem *read_reg_base = sunxi_msgbox_reg_base(chip, local_id);
|
|
void __iomem *write_reg_base = sunxi_msgbox_reg_base(chip, remote_id);
|
|
|
|
void __iomem *read_irq_en_reg = read_reg_base +
|
|
SUNXI_MSGBOX_READ_IRQ_ENABLE(local_n);
|
|
void __iomem *write_irq_en_reg = write_reg_base +
|
|
SUNXI_MSGBOX_WRITE_IRQ_ENABLE(remote_n);
|
|
|
|
u32 read_irq_en_value = readl(read_irq_en_reg);
|
|
u32 write_irq_en_value = readl(write_irq_en_reg);
|
|
|
|
for (j = 0; j < chip->hwdata->channels_max; ++j) {
|
|
read_irq_en_value &= ~(RD_IRQ_EN_MASK << RD_IRQ_EN_SHIFT(j));
|
|
write_irq_en_value &= ~(WR_IRQ_EN_MASK << WR_IRQ_EN_SHIFT(j));
|
|
}
|
|
|
|
writel(read_irq_en_value, read_irq_en_reg);
|
|
writel(write_irq_en_value, write_irq_en_reg);
|
|
}
|
|
}
|
|
|
|
static int sunxi_msgbox_resource_get(struct sunxi_msgbox *chip)
|
|
{
|
|
int ret, i;
|
|
|
|
chip->clk = devm_clk_get(chip->dev, "msgbox");
|
|
if (IS_ERR_OR_NULL(chip->clk)) {
|
|
dev_err(chip->dev, "Error: Failed to get clock\n");
|
|
return PTR_ERR(chip->clk);
|
|
}
|
|
|
|
chip->reset = devm_reset_control_get(chip->dev, NULL);
|
|
if (IS_ERR_OR_NULL(chip->reset)) {
|
|
ret = PTR_ERR(chip->reset);
|
|
dev_err(chip->dev, "Error: Failed to get reset control: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < chip->hwdata->processors_max; i++) {
|
|
chip->res = platform_get_resource(chip->pdev, IORESOURCE_MEM, i);
|
|
if (!chip->res) {
|
|
dev_err(chip->dev, "Error: Failed to get resource %d\n", i);
|
|
return -ENODEV;
|
|
}
|
|
chip->base_addr[i] = devm_ioremap_resource(chip->dev, chip->res);
|
|
if (IS_ERR(chip->base_addr[i])) {
|
|
ret = PTR_ERR(chip->base_addr[i]);
|
|
dev_err(chip->dev, "Error: Failed to map resource %d: %d\n", i, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = of_property_read_u32(chip->dev->of_node, "local_id", &chip->local_id);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to get local_id\n");
|
|
return ret;
|
|
}
|
|
|
|
chip->irq_cnt = of_irq_count(chip->dev->of_node);
|
|
chip->irq = devm_kcalloc(chip->dev, chip->irq_cnt, sizeof(int), GFP_KERNEL);
|
|
if (!chip->irq)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < chip->irq_cnt; i++) {
|
|
ret = of_irq_get(chip->dev->of_node, i);
|
|
if (ret < 0) {
|
|
dev_err(chip->dev, "of_irq_get [%d] failed: %d\n", i, ret);
|
|
return ret;
|
|
} else if (ret == 0) {
|
|
dev_err(chip->dev, "of_irq_get [%d] mapping failure\n", i);
|
|
return -EINVAL;
|
|
}
|
|
chip->irq[i] = ret;
|
|
}
|
|
|
|
for (i = 0; i < chip->irq_cnt; i++) {
|
|
ret = devm_request_irq(&chip->pdev->dev, chip->irq[i], sunxi_msgbox_handler, 0,
|
|
dev_name(chip->dev), chip);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to reuqest IRQ handler %d: %d\n", i, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_msgbox_resource_put(struct sunxi_msgbox *chip)
|
|
{
|
|
/* add you want here with sunxi_msgbox_resource_get() in the future */
|
|
return ;
|
|
}
|
|
|
|
static int sunxi_msgbox_hw_init(struct sunxi_msgbox *chip)
|
|
{
|
|
int ret;
|
|
|
|
ret = reset_control_deassert(chip->reset);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to deassert reset clk: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(chip->clk);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to enable clock: %d\n", ret);
|
|
return ret; /* reset_control_deassert no need free */
|
|
}
|
|
|
|
/* Disable all IRQs for every msgbox */
|
|
sunxi_msgbox_disable_irq(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_msgbox_hw_deinit(struct sunxi_msgbox *chip)
|
|
{
|
|
clk_disable_unprepare(chip->clk);
|
|
}
|
|
|
|
static int sunxi_msgbox_probe(struct platform_device *pdev)
|
|
{
|
|
struct mbox_chan *chans;
|
|
struct sunxi_msgbox *chip;
|
|
struct sunxi_msgbox_hwdata *priv_data;
|
|
int i, ret, processors_max_tmp;
|
|
|
|
dev_info(&pdev->dev, "%s(): sunxi msgbox start probe\n", __func__);
|
|
priv_data = (struct sunxi_msgbox_hwdata *)of_device_get_match_data(&pdev->dev);
|
|
processors_max_tmp = priv_data->processors_max;
|
|
chip = devm_kzalloc(&pdev->dev, sizeof(*chip) + (sizeof(void __iomem *) * processors_max_tmp), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->hwdata = priv_data;
|
|
chip->pdev = pdev;
|
|
chip->dev = &pdev->dev;
|
|
|
|
chans = devm_kcalloc(chip->dev, chip->hwdata->mbox_num_chans, sizeof(*chans), GFP_KERNEL);
|
|
if (!chans) {
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
for (i = 0; i < chip->hwdata->mbox_num_chans; i++)
|
|
chans[i].con_priv = chip;
|
|
|
|
ret = sunxi_msgbox_resource_get(chip);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to get resource\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sunxi_msgbox_hw_init(chip);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to init hardware\n");
|
|
goto err0;
|
|
}
|
|
|
|
chip->controller.dev = chip->dev;
|
|
chip->controller.ops = &sunxi_msgbox_chan_ops;
|
|
chip->controller.chans = chans;
|
|
chip->controller.num_chans = chip->hwdata->mbox_num_chans;
|
|
chip->controller.of_xlate = sunxi_mbox_xlate;
|
|
#if IS_ENABLED(CONFIG_AW_MAILBOX_SUPPORT_TXDONE_IRQ)
|
|
chip->controller.txdone_irq = true;
|
|
#else
|
|
chip->controller.txdone_irq = false;
|
|
chip->controller.txdone_poll = true;
|
|
chip->controller.txpoll_period = 5;
|
|
#endif
|
|
platform_set_drvdata(pdev, chip);
|
|
|
|
ret = devm_mbox_controller_register(chip->dev, &chip->controller);
|
|
if (ret) {
|
|
dev_err(chip->dev, "Error: Failed to register controller: %d\n", ret);
|
|
goto err1;
|
|
}
|
|
|
|
dev_info(chip->dev, "%s(): sunxi msgbox probe success\n", __func__);
|
|
return 0;
|
|
|
|
err1:
|
|
sunxi_msgbox_hw_deinit(chip);
|
|
|
|
err0:
|
|
sunxi_msgbox_resource_put(chip);
|
|
return ret;
|
|
}
|
|
|
|
static int sunxi_msgbox_remove(struct platform_device *pdev)
|
|
{
|
|
struct sunxi_msgbox *chip = platform_get_drvdata(pdev);
|
|
|
|
sunxi_msgbox_hw_deinit(chip);
|
|
sunxi_msgbox_resource_put(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PM)
|
|
static void sunxi_msgbox_save_regs(struct sunxi_msgbox *chip)
|
|
{
|
|
int i;
|
|
void __iomem *read_reg_base;
|
|
|
|
read_reg_base = sunxi_msgbox_reg_base(chip, chip->local_id);
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_msgbox_regs_offset); i++)
|
|
chip->regs_backup[i] = readl(read_reg_base + sunxi_msgbox_regs_offset[i]);
|
|
}
|
|
|
|
static void sunxi_msgbox_restore_regs(struct sunxi_msgbox *chip)
|
|
{
|
|
int i;
|
|
void __iomem *read_reg_base;
|
|
|
|
read_reg_base = sunxi_msgbox_reg_base(chip, chip->local_id);
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_msgbox_regs_offset); i++)
|
|
writel(chip->regs_backup[i], read_reg_base + sunxi_msgbox_regs_offset[i]);
|
|
}
|
|
|
|
static int sunxi_msgbox_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct sunxi_msgbox *chip = platform_get_drvdata(pdev);
|
|
|
|
/*
|
|
* When performing standby operations, CPUs call the device_wakeup_arm_wake_irqs() function,
|
|
* which is invoked later than the suspend() function. In addition, the clk and power of the
|
|
* msgbox will be turned off after the system sleeps, and it cannot be used after being reset
|
|
* after waking up. Therefore, during the standby process, there is no need to perform any
|
|
* operations other than saving/restoring register contents.
|
|
*/
|
|
sunxi_msgbox_save_regs(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_msgbox_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct sunxi_msgbox *chip = platform_get_drvdata(pdev);
|
|
|
|
sunxi_msgbox_restore_regs(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops sunxi_msgbox_dev_pm_ops = {
|
|
.suspend = sunxi_msgbox_suspend,
|
|
.resume = sunxi_msgbox_resume,
|
|
};
|
|
|
|
#define SUNXI_MSGBOX_DEV_PM_OPS (&sunxi_msgbox_dev_pm_ops)
|
|
#else
|
|
#define SUNXI_MSGBOX_DEV_PM_OPS NULL
|
|
#endif /* CONFIG_PM */
|
|
|
|
static struct platform_driver sunxi_msgbox_driver = {
|
|
.driver = {
|
|
.name = "sunxi-msgbox",
|
|
.of_match_table = sunxi_msgbox_of_match,
|
|
.pm = SUNXI_MSGBOX_DEV_PM_OPS,
|
|
},
|
|
.probe = sunxi_msgbox_probe,
|
|
.remove = sunxi_msgbox_remove,
|
|
};
|
|
|
|
#ifdef CONFIG_AW_RPROC_FAST_BOOT
|
|
static int __init sunxi_mailbox_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&sunxi_msgbox_driver);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit sunxi_mailbox_exit(void)
|
|
{
|
|
platform_driver_unregister(&sunxi_msgbox_driver);
|
|
}
|
|
|
|
postcore_initcall(sunxi_mailbox_init);
|
|
module_exit(sunxi_mailbox_exit);
|
|
#else
|
|
module_platform_driver(sunxi_msgbox_driver);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("Sunxi Msgbox Driver");
|
|
MODULE_AUTHOR("wujiayi <wujiayi@allwinnertech.com>");
|
|
MODULE_AUTHOR("xuminghui <xuminghui@allwinnertech.com>");
|
|
MODULE_AUTHOR("zhaiyaya <zhaiyaya@allwinnertech.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION("1.1.5");
|