[PATCH] mailbox: arm_mhu: add support for mhuv2

From: Samarth Parikh
Date: Wed May 02 2018 - 03:15:26 EST


Hi Jassi,

I am resending the patch for review, in case you have missed my previous
patch. Can you please go through it and let me know your thoughts on
the same?

ARM has launched a next version of MHU i.e. MHUv2 with its latest
subsystems. The main change is that the MHUv2 is now a distributed IP
with different peripheral views (registers) for the sender and receiver.

Another main difference is that MHUv1 duplex channels are now split into
simplex/half duplex in MHUv2. MHUv2 has a configurable number of
communication channels. There is a capability register (MSG_NO_CAP) to
find out how many channels are available in a system.

The register offsets have also changed for STAT, SET & CLEAR registers
from 0x0, 0x8 & 0x10 in MHUv1 to 0x0, 0xC & 0x8 in MHUv2 respectively.

0x0 0x4 0x8 0xC 0x1F
------------------------....-----
| STAT | | | SET | | |
------------------------....-----
Transmit Channel

0x0 0x4 0x8 0xC 0x1F
------------------------....-----
| STAT | | CLR | | | |
------------------------....-----
Receive Channel

The MHU controller can request the receiver to wake-up and once the
request is removed, the receiver may go back to sleep, but the MHU
itself does not actively puts a receiver to sleep.

So, in order to wake-up the receiver when the sender wants to send data,
the sender has to set ACCESS_REQUEST register first in order to wake-up
receiver, state of which can be detected using ACCESS_READY register.
ACCESS_REQUEST has an offset of 0xF88 & ACCESS_READY has an offset
of 0xF8C and are accessible only on any sender channel.

This patch adds necessary changes required to support the older
version of MHU & the latest MHUv2 controller. This patch also need an
update in DT binding for ARM MHU as we need a second register base
(tx base) which would be used as the send channel base.

Signed-off-by: Samarth Parikh <samarth.parikh@xxxxxxx>
---
drivers/mailbox/arm_mhu.c | 163 ++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 151 insertions(+), 12 deletions(-)

diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c
index 99befa7..d8825c5 100644
--- a/drivers/mailbox/arm_mhu.c
+++ b/drivers/mailbox/arm_mhu.c
@@ -23,6 +23,8 @@
#include <linux/module.h>
#include <linux/amba/bus.h>
#include <linux/mailbox_controller.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>

#define INTR_STAT_OFS 0x0
#define INTR_SET_OFS 0x8
@@ -33,12 +35,69 @@
#define MHU_SEC_OFFSET 0x200
#define TX_REG_OFFSET 0x100

+#define MHU_V2_REG_STAT_OFS 0x0
+#define MHU_V2_REG_CLR_OFS 0x8
+#define MHU_V2_REG_SET_OFS 0xC
+#define MHU_V2_REG_MSG_NO_CAP 0xF80
+#define MHU_V2_REG_ACC_REQ_OFS 0xF88
+#define MHU_V2_REG_ACC_RDY_OFS 0xF8C
+
+#define MHU_V2_LP_OFFSET 0x20
+#define MHU_V2_HP_OFFSET 0x0
+
#define MHU_CHANS 3

+enum mhu_ver {
+ MHU_V1 = 1,
+ MHU_V2,
+ MHU_VER_END
+};
+
+enum mhu_regs {
+ MHU_REG_STAT,
+ MHU_REG_SET,
+ MHU_REG_CLR,
+ MHU_REG_END
+};
+
+enum mhu_access_regs {
+ MHU_REG_MSG_NO_CAP,
+ MHU_REG_ACC_REQ,
+ MHU_REG_ACC_RDY,
+ MHU_REG_ACC_END
+};
+
+enum mhu_channels {
+ MHU_CHAN_LOW,
+ MHU_CHAN_HIGH,
+ MHU_CHAN_SEC,
+ MHU_CHAN_END
+};
+
+/**
+ * ARM MHU Mailbox device specific data
+ *
+ * @regs: MHU version specific array of register offset for STAT,
+ * SET & CLEAR registers.
+ * @chans: MHU version specific array of channel offset for Low
+ * Priority, High Priority & Secure channels.
+ * @acc_regs: An array of access register offsets.
+ * @tx_reg_off: Offset for TX register.
+ * @version: Version of MHU controller available in the system.
+ */
+struct mhu_data {
+ int regs[MHU_REG_END]; /* STAT, SET, CLEAR */
+ int chans[MHU_CHAN_END]; /* LP, HP, Sec */
+ int acc_regs[MHU_REG_ACC_END];
+ long int tx_reg_off;
+ uint8_t version;
+};
+
struct mhu_link {
unsigned irq;
void __iomem *tx_reg;
void __iomem *rx_reg;
+ unsigned int pchan;
};

struct arm_mhu {
@@ -46,21 +105,24 @@ struct arm_mhu {
struct mhu_link mlink[MHU_CHANS];
struct mbox_chan chan[MHU_CHANS];
struct mbox_controller mbox;
+ struct mhu_data *drvdata;
};

static irqreturn_t mhu_rx_interrupt(int irq, void *p)
{
struct mbox_chan *chan = p;
struct mhu_link *mlink = chan->con_priv;
+ struct arm_mhu *mhu = container_of(chan->mbox, struct arm_mhu, mbox);
+ struct mhu_data *mdata = mhu->drvdata;
u32 val;

- val = readl_relaxed(mlink->rx_reg + INTR_STAT_OFS);
+ val = readl_relaxed(mlink->rx_reg + mdata->regs[MHU_REG_STAT]);
if (!val)
return IRQ_NONE;

mbox_chan_received_data(chan, (void *)&val);

- writel_relaxed(val, mlink->rx_reg + INTR_CLR_OFS);
+ writel_relaxed(val, mlink->rx_reg + mdata->regs[MHU_REG_CLR]);

return IRQ_HANDLED;
}
@@ -68,7 +130,9 @@ static irqreturn_t mhu_rx_interrupt(int irq, void *p)
static bool mhu_last_tx_done(struct mbox_chan *chan)
{
struct mhu_link *mlink = chan->con_priv;
- u32 val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS);
+ struct arm_mhu *mhu = container_of(chan->mbox, struct arm_mhu, mbox);
+ struct mhu_data *mdata = mhu->drvdata;
+ u32 val = readl_relaxed(mlink->tx_reg + mdata->regs[MHU_REG_STAT]);

return (val == 0);
}
@@ -76,9 +140,11 @@ static bool mhu_last_tx_done(struct mbox_chan *chan)
static int mhu_send_data(struct mbox_chan *chan, void *data)
{
struct mhu_link *mlink = chan->con_priv;
+ struct arm_mhu *mhu = container_of(chan->mbox, struct arm_mhu, mbox);
+ struct mhu_data *mdata = mhu->drvdata;
u32 *arg = data;

- writel_relaxed(*arg, mlink->tx_reg + INTR_SET_OFS);
+ writel_relaxed(*arg, mlink->tx_reg + mdata->regs[MHU_REG_SET]);

return 0;
}
@@ -86,11 +152,18 @@ static int mhu_send_data(struct mbox_chan *chan, void *data)
static int mhu_startup(struct mbox_chan *chan)
{
struct mhu_link *mlink = chan->con_priv;
+ struct arm_mhu *mhu = container_of(chan->mbox, struct arm_mhu, mbox);
+ struct mhu_data *mdata = mhu->drvdata;
u32 val;
int ret;

- val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS);
- writel_relaxed(val, mlink->tx_reg + INTR_CLR_OFS);
+ if (mdata->version == MHU_V2)
+ writel_relaxed(0x1, mlink->tx_reg
+ + (mdata->acc_regs[MHU_REG_ACC_REQ]
+ - (mdata->chans[mlink->pchan])));
+
+ val = readl_relaxed(mlink->tx_reg + mdata->regs[MHU_REG_STAT]);
+ writel_relaxed(val, mlink->tx_reg + mdata->regs[MHU_REG_CLR]);

ret = request_irq(mlink->irq, mhu_rx_interrupt,
IRQF_SHARED, "mhu_link", chan);
@@ -106,6 +179,13 @@ static int mhu_startup(struct mbox_chan *chan)
static void mhu_shutdown(struct mbox_chan *chan)
{
struct mhu_link *mlink = chan->con_priv;
+ struct arm_mhu *mhu = container_of(chan->mbox, struct arm_mhu, mbox);
+ struct mhu_data *mdata = mhu->drvdata;
+
+ if (mdata->version == MHU_V2)
+ writel_relaxed(0x0, mlink->tx_reg
+ + (mdata->acc_regs[MHU_REG_ACC_REQ]
+ - (mdata->chans[mlink->pchan])));

free_irq(mlink->irq, chan);
}
@@ -122,7 +202,15 @@ static int mhu_probe(struct amba_device *adev, const struct amba_id *id)
int i, err;
struct arm_mhu *mhu;
struct device *dev = &adev->dev;
- int mhu_reg[MHU_CHANS] = {MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET};
+ void __iomem *tx_base;
+ struct device_node *np = dev->of_node;
+ struct mhu_data *mdata = id->data;
+ unsigned int pchans = MHU_CHANS;
+
+ if (!mdata) {
+ dev_err(dev, "device data not found\n");
+ return -EINVAL;
+ }

/* Allocate memory for device */
mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL);
@@ -135,20 +223,45 @@ static int mhu_probe(struct amba_device *adev, const struct amba_id *id)
return PTR_ERR(mhu->base);
}

- for (i = 0; i < MHU_CHANS; i++) {
+ if (mdata->version == MHU_V2) {
+ tx_base = of_iomap(np, 1);
+ if (!tx_base) {
+ dev_err(dev, "failed to map tx registers\n");
+ return -ENOMEM;
+ }
+
+ mdata->tx_reg_off = tx_base - mhu->base;
+ pchans = readl_relaxed(tx_base
+ + mdata->acc_regs[MHU_REG_MSG_NO_CAP]);
+ if (pchans == 0 || pchans > MHU_CHANS) {
+ dev_err(dev, "invalid number of channels\n");
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < pchans; i++) {
mhu->chan[i].con_priv = &mhu->mlink[i];
- mhu->mlink[i].irq = adev->irq[i];
- mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i];
- mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET;
+ mhu->mlink[i].pchan = i;
+ int irq = mhu->mlink[i].irq = adev->irq[i];
+
+ if (irq <= 0) {
+ dev_dbg(dev, "No IRQ found for Channel %d\n", i);
+ continue;
+ }
+
+ mhu->mlink[i].rx_reg = mhu->base + mdata->chans[i];
+ mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg
+ + mdata->tx_reg_off;
}

mhu->mbox.dev = dev;
mhu->mbox.chans = &mhu->chan[0];
- mhu->mbox.num_chans = MHU_CHANS;
+ mhu->mbox.num_chans = pchans;
mhu->mbox.ops = &mhu_ops;
mhu->mbox.txdone_irq = false;
mhu->mbox.txdone_poll = true;
mhu->mbox.txpoll_period = 1;
+ mhu->drvdata = mdata;

amba_set_drvdata(adev, mhu);

@@ -171,10 +284,36 @@ static int mhu_remove(struct amba_device *adev)
return 0;
}

+static struct mhu_data arm_mhuv2_data = {
+ .regs = { MHU_V2_REG_STAT_OFS, MHU_V2_REG_SET_OFS, MHU_V2_REG_CLR_OFS },
+ .chans = { MHU_V2_LP_OFFSET, MHU_V2_HP_OFFSET },
+ .acc_regs = { MHU_V2_REG_MSG_NO_CAP, MHU_V2_REG_ACC_REQ_OFS,
+ MHU_V2_REG_ACC_RDY_OFS },
+ .version = MHU_V2,
+};
+
+static struct mhu_data arm_mhuv1_data = {
+ .regs = { INTR_STAT_OFS, INTR_SET_OFS, INTR_CLR_OFS },
+ .chans = { MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET },
+ .tx_reg_off = TX_REG_OFFSET,
+ .version = MHU_V1,
+};
+
static struct amba_id mhu_ids[] = {
{
+ .id = 0x4b0d1,
+ .mask = 0xfffff,
+ .data = (void *)&arm_mhuv2_data,
+ },
+ {
+ .id = 0xbb0d1,
+ .mask = 0xfffff,
+ .data = (void *)&arm_mhuv2_data,
+ },
+ {
.id = 0x1bb098,
.mask = 0xffffff,
+ .data = (void *)&arm_mhuv1_data,
},
{ 0, 0 },
};
--
2.7.4

IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.