From: Morten Borup Petersen <[email protected]>

This commit adds a driver for the ARM MHUv2 (Message Handling Unit).
The driver registers itself as a mailbox controller within the common
mailbox framework.

This commit implements the single-word transport protocol;
In single-word mode, the mailbox controller will provide a mailbox for each
channel window available in the MHU device.
Transmitting and receiving data through the mailbox framework in
single-word mode is done through a struct arm_mbox_msg.

Signed-off-by: Morten Borup Petersen <[email protected]>
Signed-off-by: Tushar Khandelwal <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
 drivers/mailbox/Kconfig                  |   7 +
 drivers/mailbox/Makefile                 |   2 +
 drivers/mailbox/arm_mhu_v2.c             | 735 +++++++++++++++++++++++
 include/linux/mailbox/arm-mbox-message.h |  37 ++
 4 files changed, 781 insertions(+)
 create mode 100644 drivers/mailbox/arm_mhu_v2.c
 create mode 100644 include/linux/mailbox/arm-mbox-message.h

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index e63d29a95e76..6808bba5bf9b 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -15,6 +15,13 @@ config ARM_MHU
          The controller has 3 mailbox channels, the last of which can be
          used in Secure mode only.
 
+config ARM_MHU_V2
+       tristate "ARM MHUv2 Mailbox"
+       depends on ARM_AMBA
+       help
+         Say Y here if you want to build the ARM MHUv2 controller driver,
+         which provides unidirectional mailboxes between processing elements.
+
 config PLATFORM_MHU
        tristate "Platform MHU Mailbox"
        depends on OF
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index f15788ccc572..6edef52d74bb 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -7,6 +7,8 @@ obj-$(CONFIG_MAILBOX_TEST)      += mailbox-test.o
 
 obj-$(CONFIG_ARM_MHU)          += arm_mhu.o
 
+obj-$(CONFIG_ARM_MHU_V2)       += arm_mhu_v2.o
+
 obj-$(CONFIG_PLATFORM_MHU)     += platform_mhu.o
 
 obj-$(CONFIG_PL320_MBOX)       += pl320-ipc.o
diff --git a/drivers/mailbox/arm_mhu_v2.c b/drivers/mailbox/arm_mhu_v2.c
new file mode 100644
index 000000000000..a0af683b83a2
--- /dev/null
+++ b/drivers/mailbox/arm_mhu_v2.c
@@ -0,0 +1,735 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Message Handling Unit Version 2 (MHUv2) driver
+ *
+ * An MHU device may function in one of three transport protocol modes
+ * (single-word, multi-word and doorbell).
+ * This transport protocol should be specified in the device tree entry for the
+ * device. The transport protocol determines how the underlying hardware
+ * resources of the device are utilized when transmitting data.
+ *
+ * The arm MHUv2 driver registers as a mailbox controller with the common
+ * mailbox framework. Each mailbox channel represents a separate virtual
+ * communication channel through the MHU device.
+ * The number of registered mailbox channels is dependent on both the
+ * underlying hardware - mainly the number of channel windows within each MHU
+ * frame, as well as the selected transport protocol.
+ *
+ * Copyright (C) 2019 Arm Ltd.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/amba/bus.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox/arm-mbox-message.h>
+#include <linux/of_address.h>
+#include <linux/interrupt.h>
+
+/* Maximum number of channel windows */
+#define MHUV2_CHANNEL_MAX 124
+/* Number of combined interrupt status registers */
+#define MHUV2_CMB_INT_ST_REG_CNT 4
+#define MHUV2_CH_UNKNOWN -1
+
+/* Channel window status register type */
+typedef u32 mhuv2_stat_reg_t;
+#define MHUV2_STAT_BYTES sizeof(mhuv2_stat_reg_t)
+#define MHUV2_STAT_BITS (MHUV2_STAT_BYTES * __CHAR_BIT__)
+
+#define LSB_MASK(n) ((1 << (n)) - 1)
+
+#ifndef PAD
+#define _PADLINE(line) pad##line
+#define _XSTR(line) _PADLINE(line)
+#define PAD _XSTR(__LINE__)
+#endif
+
+/* ====== Arm MHUv2 register defines ====== */
+
+/* Register Message Handling Unit Configuration fields */
+struct MHU_CFG_t {
+       u32 NUM_CH : 7;
+       u32 PAD : 25;
+} __packed;
+
+/* Register Implementer Identification fields */
+struct IIDR_t {
+       u32 IMPLEMENTER : 12;
+       u32 REVISION : 4;
+       u32 VARIANT : 4;
+       u32 PRODUCT_ID : 12;
+} __packed;
+
+/* Register Architecture Identification Register fields */
+struct AIDR_t {
+       u32 ARCH_MINOR_REV : 4;
+       u32 ARCH_MAJOR_REV : 4;
+       u32 PAD : 24;
+} __packed;
+
+/* register Interrupt Status fields */
+struct INT_ST_t {
+       u32 NR2R : 1;
+       u32 R2NR : 1;
+       u32 PAD : 30;
+} __packed;
+
+/* Register Interrupt Clear fields */
+struct INT_CLR_t {
+       u32 NR2R : 1;
+       u32 R2NR : 1;
+       u32 PAD : 30;
+} __packed;
+
+/* Register Interrupt Enable fields */
+struct INT_EN_t {
+       u32 R2NR : 1;
+       u32 NR2R : 1;
+       u32 CHCOMB : 1;
+       u32 PAD : 29;
+} __packed;
+
+/* Sender Channel Window fields */
+struct mhu2_send_channel_reg {
+       mhuv2_stat_reg_t STAT;
+       u8 PAD[0xC - 0x4];
+       mhuv2_stat_reg_t STAT_SET;
+       u8 PAD[0x20 - 0x10];
+} __packed;
+
+/* Sender frame register fields */
+struct mhu2_send_frame_reg {
+       struct mhu2_send_channel_reg channel[MHUV2_CHANNEL_MAX];
+       struct MHU_CFG_t MHU_CFG;
+       u32 RESP_CFG;
+       u32 ACCESS_REQUEST;
+       u32 ACCESS_READY;
+       struct INT_ST_t INT_ST;
+       struct INT_CLR_t INT_CLR;
+       struct INT_EN_t INT_EN;
+       u32 RESERVED0;
+       u32 CHCOMB_INT_ST[MHUV2_CMB_INT_ST_REG_CNT];
+       u8 PAD[0xFC8 - 0xFB0];
+       struct IIDR_t IIDR;
+       struct AIDR_t AIDR;
+} __packed;
+
+/* Receiver Channel Window fields */
+struct mhu2_recv_channel_reg {
+       mhuv2_stat_reg_t STAT;
+       mhuv2_stat_reg_t STAT_PEND;
+       mhuv2_stat_reg_t STAT_CLEAR;
+       u8 RESERVED0[0x10 - 0x0C];
+       mhuv2_stat_reg_t MASK;
+       mhuv2_stat_reg_t MASK_SET;
+       mhuv2_stat_reg_t MASK_CLEAR;
+       u8 PAD[0x20 - 0x1C];
+} __packed;
+
+/* Receiver frame register fields */
+struct mhu2_recv_frame_reg {
+       struct mhu2_recv_channel_reg channel[MHUV2_CHANNEL_MAX];
+       struct MHU_CFG_t MHU_CFG;
+       u8 RESERVED0[0xF90 - 0xF84];
+       struct INT_ST_t INT_ST;
+       struct INT_CLR_t INT_CLR;
+       struct INT_EN_t INT_EN;
+       u32 PAD;
+       mhuv2_stat_reg_t CHCOMB_INT_ST[MHUV2_CMB_INT_ST_REG_CNT];
+       u8 RESERVED2[0xFC8 - 0xFB0];
+       struct IIDR_t IIDR;
+       struct AIDR_t AIDR;
+} __packed;
+
+/* ====== Arm MHUv2 device tree property identifiers ====== */
+
+static const char *const mhuv2_protocol_dt_identifiers[] = { "single-word",
+                                                            "multi-word",
+                                                            "doorbell" };
+
+static const char *const mhuv2_frame_dt_identifiers[] = { "receiver",
+                                                         "sender" };
+
+/* ====== Arm MHUv2 data structures ====== */
+
+enum mhuv2_transport_protocol { SINGLE_WORD, MULTI_WORD, DOORBELL };
+
+enum mhuv2_frame { RECEIVER_FRAME, SENDER_FRAME };
+
+/**
+ * Arm MHUv2 operations
+ *
+ * Each transport protocol must provide an implementation of the operations
+ * presented in this structure.
+ * Most operations present a struct mbox_chan* as argument. This channel will
+ * correspond to one of the virtual channels within the MHU device. What
+ * constitutes a channel within the MHU device is dependent on the transport
+ * protocol.
+ */
+struct arm_mhuv2;
+struct mhuv2_ops {
+       int (*read_data)(struct arm_mhuv2 *mhuv2, struct mbox_chan *chan,
+                        struct arm_mbox_msg *msg);
+       int (*clear_data)(struct arm_mhuv2 *mhuv2, struct mbox_chan *chan,
+                         struct arm_mbox_msg *msg);
+       int (*send_data)(struct arm_mhuv2 *mhuv2, struct mbox_chan *chan,
+                        const struct arm_mbox_msg *msg);
+       int (*setup)(struct arm_mhuv2 *mhuv2);
+       int (*last_tx_done)(struct arm_mhuv2 *mhuv2, struct mbox_chan *chan);
+       struct mbox_chan *(*get_active_mbox_chan)(struct arm_mhuv2 *mhuv2);
+};
+
+/**
+ * Arm MHUv2 mailbox channel information
+ *
+ * A channel contains a notion of its index within the array of mailbox 
channels
+ * which a mailbox controller allocates.
+ */
+struct arm_mhuv2_mbox_chan_priv {
+       u32 ch_idx;
+};
+
+#define mhuv2_chan_idx(_chanptr)                                               
\
+       (((struct arm_mhuv2_mbox_chan_priv *)(_chanptr)->con_priv)->ch_idx)
+
+/**
+ * Arm MHUv2 mailbox controller data
+ *
+ * @reg:       Base address of the register mapping region
+ * @protocol:  Transport protocol, derived from device tree
+ * @frame:     Frame type, derived from device tree
+ * @irq:       Interrupt, only valid for receiver frames
+ * @mbox:      Mailbox controller belonging to the MHU frame
+ * @ops:       Pointer to transport-protocol specific operations
+ * @dev:       Device to which it is attached
+ */
+struct arm_mhuv2 {
+       union {
+               struct mhu2_send_frame_reg __iomem *send;
+               struct mhu2_recv_frame_reg __iomem *recv;
+       } reg;
+       enum mhuv2_transport_protocol protocol;
+       enum mhuv2_frame frame;
+       unsigned int irq;
+       struct mbox_controller mbox;
+       struct mhuv2_ops *ops;
+       struct device *dev;
+};
+
+#define mhu2_from_mbox_ctrl(_mbox) container_of(_mbox, struct arm_mhuv2, mbox)
+#define mhu2_from_mbox_chan(_chan) mhu2_from_mbox_ctrl(_chan->mbox)
+
+/* Macro for reading a bitfield within a physically mapped packed struct */
+#define readl_relaxed_bitfield(_regptr, _field)                                
\
+       ({                                                                     \
+               mhuv2_stat_reg_t _regval;                                      \
+               BUILD_BUG_ON(sizeof(*(_regptr)) > sizeof(typeof(_regval)));    \
+               _regval = readl_relaxed((_regptr));                            \
+               (*(typeof((_regptr)))(&_regval))._field;                       \
+       })
+
+/* Macro for writing a bitfield within a physically mapped packed struct */
+#define writel_relaxed_bitfield(_value, _regptr, _field)                       
\
+       ({                                                                     \
+               mhuv2_stat_reg_t _regval;                                      \
+               BUILD_BUG_ON(sizeof(*_regptr) > sizeof(typeof(_regval)));      \
+               _regval = readl_relaxed(_regptr);                              \
+               (*(typeof(_regptr))(&_regval))._field = _value;                \
+               writel_relaxed(_regval, _regptr);                              \
+       })
+
+static inline int __find_set_bit(uint32_t val)
+{
+       const uint32_t trailing_zeros = __builtin_ctz(val);
+       return trailing_zeros == 32 ? -1 : trailing_zeros;
+}
+
+/**
+ * Get index of a set bit within the combined interrupt status registers
+ *
+ * The function will calculate the index being the offset from the LSB of the
+ * first combined interrupt status register.
+ *
+ */
+static inline int mhuv2_combint_idx(struct arm_mhuv2 *mhuv2)
+{
+       int ch_idx = 0;
+       int set_bit_index, reg_idx;
+
+       for (reg_idx = 0; reg_idx < MHUV2_CMB_INT_ST_REG_CNT; reg_idx++) {
+               mhuv2_stat_reg_t stat_reg;
+
+               stat_reg =
+                       readl_relaxed(&mhuv2->reg.recv->CHCOMB_INT_ST[reg_idx]);
+               set_bit_index = __find_set_bit(stat_reg);
+               if (set_bit_index == -1) {
+                       ch_idx += MHUV2_STAT_BITS;
+               } else {
+                       ch_idx += set_bit_index;
+                       break;
+               }
+       }
+       return (ch_idx >= (MHUV2_CMB_INT_ST_REG_CNT * MHUV2_STAT_BITS) ?
+                       MHUV2_CH_UNKNOWN :
+                       ch_idx);
+}
+
+/* ================ Single word transport protocol operations =============== 
*/
+static inline int mhuv20_get_non_zero_ch_idx(struct arm_mhuv2 *mhuv2)
+{
+       /* Locate a channel window with a non-zero STAT register */
+       int ch_idx;
+       int ch = MHUV2_CH_UNKNOWN;
+
+       for (ch_idx = 0;
+            ch_idx < readl_relaxed_bitfield(&mhuv2->reg.recv->MHU_CFG, NUM_CH);
+            ch_idx++) {
+               if (readl_relaxed(&mhuv2->reg.recv->channel[ch].STAT)) {
+                       ch = ch_idx;
+                       break;
+               }
+       }
+       return ch;
+}
+
+static inline int mhuv2_get_non_zero_ch_idx(struct arm_mhuv2 *mhuv2)
+{
+       /* Identify index of channel window containing non-zero data */
+       switch (readl_relaxed_bitfield(&mhuv2->reg.recv->AIDR,
+                                      ARCH_MINOR_REV)) {
+       case 1:
+               return mhuv2_combint_idx(mhuv2);
+       default:
+               return mhuv20_get_non_zero_ch_idx(mhuv2);
+       }
+}
+
+static inline int mhuv2_read_data_single_word(struct arm_mhuv2 *mhuv2,
+                                             struct mbox_chan *chan,
+                                             struct arm_mbox_msg *msg)
+{
+       const u32 ch_idx = mhuv2_chan_idx(chan);
+
+       msg->data = kzalloc(MHUV2_STAT_BYTES, GFP_KERNEL);
+       if (!msg->data)
+               return -ENOMEM;
+
+       *(mhuv2_stat_reg_t *)msg->data =
+               readl_relaxed(&mhuv2->reg.recv->channel[ch_idx].STAT);
+       msg->len = MHUV2_STAT_BYTES;
+       return 0;
+}
+
+static inline int mhuv2_clear_data_single_word(struct arm_mhuv2 *mhuv2,
+                                              struct mbox_chan *chan,
+                                              struct arm_mbox_msg *msg)
+{
+       const u32 ch_idx = mhuv2_chan_idx(chan);
+
+       writel_relaxed(readl_relaxed(&mhuv2->reg.recv->channel[ch_idx].STAT),
+                      &mhuv2->reg.recv->channel[ch_idx].STAT_CLEAR);
+       return 0;
+}
+
+static inline int mhuv2_send_data_single_word(struct arm_mhuv2 *mhuv2,
+                                             struct mbox_chan *chan,
+                                             const struct arm_mbox_msg *msg)
+{
+       const u32 ch_idx = mhuv2_chan_idx(chan);
+       int bytes_left = msg->len;
+       char *data = msg->data;
+
+       if (ch_idx >= readl_relaxed_bitfield(&mhuv2->reg.recv->MHU_CFG, NUM_CH))
+               return -ENODEV;
+
+       while (bytes_left > 0) {
+               mhuv2_stat_reg_t word = *(mhuv2_stat_reg_t *)(data);
+
+               if (bytes_left < MHUV2_STAT_BYTES)
+                       word &= LSB_MASK(bytes_left * __CHAR_BIT__);
+
+               if (!word) {
+                       dev_err(mhuv2->dev,
+                               "Data transmitted in single-word mode must be 
non-zero\n");
+                       return -EINVAL;
+               }
+               writel_relaxed(word,
+                              &mhuv2->reg.send->channel[ch_idx].STAT_SET);
+               while (readl_relaxed(&mhuv2->reg.send->channel[ch_idx].STAT))
+                       continue;
+               bytes_left -= MHUV2_STAT_BYTES;
+               data += MHUV2_STAT_BYTES;
+       }
+
+       return 0;
+}
+
+static inline int mhuv2_last_tx_done_single_word(struct arm_mhuv2 *mhuv2,
+                                                struct mbox_chan *chan)
+{
+       const u32 ch_idx = mhuv2_chan_idx(chan);
+
+       return readl_relaxed(&mhuv2->reg.send->channel[ch_idx].STAT) == 0;
+}
+
+static inline int mhuv2_setup_single_word(struct arm_mhuv2 *mhuv2)
+{
+       int i;
+       const u32 channel_windows =
+               readl_relaxed_bitfield(mhuv2->frame == RECEIVER_FRAME ?
+                                              &mhuv2->reg.recv->MHU_CFG :
+                                              &mhuv2->reg.send->MHU_CFG,
+                                      NUM_CH);
+
+       mhuv2->mbox.num_chans = channel_windows;
+       mhuv2->mbox.chans =
+               devm_kzalloc(mhuv2->dev,
+                            mhuv2->mbox.num_chans * sizeof(struct mbox_chan),
+                            GFP_KERNEL);
+
+       for (i = 0; i < mhuv2->mbox.num_chans; i++) {
+               mhuv2->mbox.chans[i].con_priv =
+                       devm_kzalloc(mhuv2->dev,
+                                    sizeof(struct arm_mhuv2_mbox_chan_priv),
+                                    GFP_KERNEL);
+               mhuv2_chan_idx(&mhuv2->mbox.chans[i]) = i;
+       }
+
+       if (mhuv2->frame == RECEIVER_FRAME) {
+               /* Ensure all status registers are unmasked */
+               for (i = 0; i < channel_windows; i++) {
+                       writel_relaxed(0x0,
+                                      &mhuv2->reg.recv->channel[i].MASK_SET);
+               }
+       }
+
+       return 0;
+}
+
+static inline struct mbox_chan *
+       mhuv2_get_active_mbox_chan_single_word(struct arm_mhuv2 *mhuv2)
+{
+       const u32 ch_idx = mhuv2_get_non_zero_ch_idx(mhuv2);
+
+       if (ch_idx >= mhuv2->mbox.num_chans) {
+               dev_err(mhuv2->dev,
+                       "Invalid active channel in single word mode\n");
+               return ERR_PTR(-EINVAL);
+       }
+       return &mhuv2->mbox.chans[ch_idx];
+}
+
+static const struct mhuv2_ops mhuv2_single_word_ops = {
+       .read_data = mhuv2_read_data_single_word,
+       .clear_data = mhuv2_clear_data_single_word,
+       .send_data = mhuv2_send_data_single_word,
+       .setup = mhuv2_setup_single_word,
+       .last_tx_done = mhuv2_last_tx_done_single_word,
+       .get_active_mbox_chan = mhuv2_get_active_mbox_chan_single_word,
+};
+/* ========================================================================== 
*/
+
+/*
+ * MHUv2 receiver interrupt service routine
+ *
+ * This routine will be called whenever a reception interrupt is raised on the
+ * MHU device. Given that an MHU device may manage multiple mailboxes, it is
+ * up to the protocol-specific operations to determine:
+ * - What is the active mailbox channel
+ * - Read the data within the MHU corresponding to the channel
+ * - Clear the data within the MHU corresponding to the channel
+ *
+ * These operations must also ensure to not overwrite any data which may belong
+ * to a different mailbox channel. For instance, if data is received in two
+ * channel windows in single-word mode, the ISR will read and clear the data
+ * from one of these channel windows within a pass. This will result in a 
status
+ * register being non-zero upon returning from this routine, which in turn
+ * will keep the interrupt asserted for a second round.
+ */
+static irqreturn_t mhuv2_rx_interrupt(int irq, void *data)
+{
+       struct arm_mbox_msg msg;
+       int status;
+       struct arm_mhuv2 *mhuv2 = data;
+       struct mbox_chan *chan = mhuv2->ops->get_active_mbox_chan(mhuv2);
+
+       msg.data = NULL;
+       msg.len = 0;
+
+       status = mhuv2->ops->read_data(mhuv2, chan, &msg);
+       if (status != 0)
+               goto rx_exit;
+
+       if (!chan->cl) {
+               dev_warn(
+                       mhuv2->dev,
+                       "Warning: Received data on channel not currently 
attached to a mailbox client\n");
+       } else {
+               mbox_chan_received_data(chan, (void *)&msg);
+       }
+
+       status = mhuv2->ops->clear_data(mhuv2, chan, &msg);
+
+rx_exit:
+       kfree(msg.data);
+
+       return status == 0 ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static bool mhuv2_last_tx_done(struct mbox_chan *chan)
+{
+       struct arm_mhuv2 *mhuv2 = mhu2_from_mbox_chan(chan);
+
+       return mhuv2->ops->last_tx_done(mhuv2, chan);
+}
+
+static int mhuv2_send_data(struct mbox_chan *chan, void *data)
+{
+       struct arm_mhuv2 *mhuv2 = mhu2_from_mbox_chan(chan);
+       struct arm_mbox_msg *msg = data;
+       int ret;
+
+       if (!mhuv2->ops->last_tx_done(mhuv2, chan))
+               return -EBUSY;
+
+       ret = mhuv2->ops->send_data(mhuv2, chan, msg);
+       return ret;
+}
+
+static int mhuv2_startup(struct mbox_chan *chan)
+{
+       struct arm_mhuv2 *mhuv2 = mhu2_from_mbox_ctrl(chan->mbox);
+
+       writel_relaxed(0x1, &mhuv2->reg.send->ACCESS_REQUEST);
+       while (!readl_relaxed(&mhuv2->reg.send->ACCESS_READY))
+               continue;
+
+       return 0;
+}
+
+static void mhuv2_shutdown(struct mbox_chan *chan)
+{
+       struct arm_mhuv2 *mhuv2 = mhu2_from_mbox_ctrl(chan->mbox);
+
+       writel_relaxed(0x0, &mhuv2->reg.send->ACCESS_REQUEST);
+}
+
+static int mhuv2_recv_startup(struct mbox_chan *chan)
+{
+       return 0;
+}
+
+static void mhuv2_recv_shutdown(struct mbox_chan *chan)
+{
+}
+
+static int mhuv2_recv_send_data(struct mbox_chan *chan, void *data)
+{
+       dev_err(chan->mbox->dev,
+               "Trying to transmit on a receiver MHU frame\n");
+       return -EIO;
+}
+
+static bool mhuv2_recv_last_tx_done(struct mbox_chan *chan)
+{
+       dev_err(chan->mbox->dev, "Trying to Tx poll on a receiver MHU frame\n");
+       return true;
+}
+
+static const struct mbox_chan_ops mhuv2_receiver_ops = {
+       .send_data = mhuv2_recv_send_data,
+       .startup = mhuv2_recv_startup,
+       .shutdown = mhuv2_recv_shutdown,
+       .last_tx_done = mhuv2_recv_last_tx_done,
+};
+
+static const struct mbox_chan_ops mhuv2_sender_ops = {
+       .send_data = mhuv2_send_data,
+       .startup = mhuv2_startup,
+       .shutdown = mhuv2_shutdown,
+       .last_tx_done = mhuv2_last_tx_done,
+};
+
+static struct mbox_chan *mhuv2_mbox_of_xlate(struct mbox_controller *ctrl,
+                                            const struct of_phandle_args *pa)
+{
+       struct mbox_chan *chan;
+
+       if (pa->args_count != 1)
+               return ERR_PTR(-EINVAL);
+
+       if (pa->args[0] >= ctrl->num_chans)
+               return ERR_PTR(-ENOENT);
+
+       chan = &ctrl->chans[pa->args[0]];
+       return chan;
+}
+
+static int mhuv2_probe(struct amba_device *adev, const struct amba_id *id)
+{
+       int err;
+       struct device *dev = &adev->dev;
+       const struct device_node *np = dev->of_node;
+       struct arm_mhuv2 *mhuv2;
+       const char *str;
+
+       /* Allocate memory for device */
+       mhuv2 = devm_kzalloc(dev, sizeof(*mhuv2), GFP_KERNEL);
+       if (!mhuv2)
+               return -ENOMEM;
+
+       mhuv2->dev = dev;
+
+       /* Retrieve MHU frame specifier from device tree node */
+       err = of_property_read_string(np, "mhu-frame", &str);
+       if (err) {
+               dev_err(dev, "Probe failed: MHU frame not specified.");
+               return -ENODEV;
+       } else if (strcmp(str, mhuv2_frame_dt_identifiers[SENDER_FRAME]) == 0) {
+               mhuv2->frame = SENDER_FRAME;
+       } else if (strcmp(str, mhuv2_frame_dt_identifiers[RECEIVER_FRAME]) ==
+                  0) {
+               mhuv2->frame = RECEIVER_FRAME;
+       } else {
+               dev_err(dev,
+                       "Probe failed; '%s' is not a valid MHU frame 
specifier\n",
+                       str);
+               return -ENODEV;
+       }
+
+       /* Retrieve transport protocol specifier from device tree node */
+       err = of_property_read_string(np, "mhu-protocol", &str);
+       if (err) {
+               dev_err(dev,
+                        "Probe failed: no transport protocol specified\n");
+               return -ENODEV;
+       } else if (strcmp(str, mhuv2_protocol_dt_identifiers[SINGLE_WORD]) ==
+                  0) {
+               mhuv2->protocol = SINGLE_WORD;
+       } else if (strcmp(str, mhuv2_protocol_dt_identifiers[MULTI_WORD]) ==
+                  0) {
+               mhuv2->protocol = MULTI_WORD;
+       } else if (strcmp(str, mhuv2_protocol_dt_identifiers[DOORBELL]) == 0) {
+               mhuv2->protocol = DOORBELL;
+       } else {
+               dev_err(dev,
+                       "Probe failed: '%s' is not a valid transport protocol 
specifier\n",
+                       str);
+               return -ENODEV;
+       }
+
+       /* Get MHU type specific properties from device tree */
+       if (mhuv2->frame == RECEIVER_FRAME) {
+               mhuv2->reg.recv = (struct mhu2_recv_frame_reg *)of_iomap(
+                       (struct device_node *)np, 0);
+               if (!mhuv2->reg.recv)
+                       goto io_fail;
+               mhuv2->irq = adev->irq[0];
+       } else {
+               mhuv2->reg.send = (struct mhu2_send_frame_reg *)of_iomap(
+                       (struct device_node *)np, 0);
+               if (!mhuv2->reg.send)
+                       goto io_fail;
+       }
+
+       /* Assign transport protocol-specific operations */
+       switch (mhuv2->protocol) {
+       case SINGLE_WORD:
+               mhuv2->ops = &mhuv2_single_word_ops;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       /* Mailbox controller setup */
+       mhuv2->mbox.dev = dev;
+       mhuv2->mbox.txdone_irq = false;
+       mhuv2->mbox.txdone_poll = true;
+       mhuv2->mbox.txpoll_period = 1;
+       mhuv2->mbox.of_xlate = mhuv2_mbox_of_xlate;
+       mhuv2->mbox.ops = mhuv2->frame == SENDER_FRAME ? &mhuv2_sender_ops :
+                                                        &mhuv2_receiver_ops;
+       /*
+        * Transport protocol specific setup
+        * Setup function _must_ allocate mailbox channels according to the
+        * number of channels provided in the given transport protocol mode.
+        */
+       err = mhuv2->ops->setup(mhuv2);
+       if (err)
+               return err;
+
+       /* Request an interrupt if this is a receiver frame */
+       if (mhuv2->frame == RECEIVER_FRAME) {
+               err = request_irq(mhuv2->irq, mhuv2_rx_interrupt, IRQF_SHARED,
+                                 "mhuv2_link", mhuv2);
+               if (err) {
+                       dev_err(dev, "unable to acquire IRQ %d\n", mhuv2->irq);
+                       return err;
+               }
+               /*
+                * For minor version 1 and forward, the combined interrupt of
+                * the receiver frame must be explicitly enabled during startup.
+                */
+               if (readl_relaxed_bitfield(&mhuv2->reg.recv->AIDR,
+                                          ARCH_MINOR_REV) > 0) {
+                       writel_relaxed_bitfield(1, &mhuv2->reg.recv->INT_EN,
+                                               CHCOMB);
+               }
+       }
+
+       amba_set_drvdata(adev, mhuv2);
+
+       err = mbox_controller_register(&mhuv2->mbox);
+       if (err) {
+               dev_err(dev, "failed to register ARM MHUv2 driver %d\n", err);
+               iounmap(mhuv2->frame == RECEIVER_FRAME ? mhuv2->reg.recv :
+                                                        mhuv2->reg.send);
+               return err;
+       }
+
+       dev_info(dev, "ARM MHUv2 %s frame (%s) Mailbox driver registered\n",
+                mhuv2_frame_dt_identifiers[mhuv2->frame],
+                mhuv2_protocol_dt_identifiers[mhuv2->protocol]);
+
+       return 0;
+
+io_fail:
+       dev_err(dev, "Probe failed: failed to map '%s' frame\n",
+               mhuv2_frame_dt_identifiers[mhuv2->frame]);
+       iounmap(mhuv2->frame == RECEIVER_FRAME ? mhuv2->reg.recv :
+                                                mhuv2->reg.send);
+       return -ENOMEM;
+}
+
+static int mhuv2_remove(struct amba_device *adev)
+{
+       return 0;
+}
+
+static struct amba_id mhuv2_ids[] = {
+       {
+               .id = 0x4b0d1,
+               .mask = 0xfffff,
+       },
+       {
+               .id = 0xbb0d1,
+               .mask = 0xfffff,
+       },
+       { 0, 0 },
+};
+MODULE_DEVICE_TABLE(amba, mhuv2_ids);
+
+static struct amba_driver arm_mhuv2_driver = {
+       .drv = {
+               .name   = "mhuv2",
+       },
+       .id_table       = mhuv2_ids,
+       .probe          = mhuv2_probe,
+       .remove         = mhuv2_remove,
+};
+module_amba_driver(arm_mhuv2_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ARM MHUv2 Driver");
+MODULE_AUTHOR("Morten Borup Petersen <[email protected]>");
diff --git a/include/linux/mailbox/arm-mbox-message.h 
b/include/linux/mailbox/arm-mbox-message.h
new file mode 100644
index 000000000000..112b4f927c1a
--- /dev/null
+++ b/include/linux/mailbox/arm-mbox-message.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arm Mailbox Message
+ *
+ * The Arm mailbox message structure is used to pass data- and length
+ * information between a mailbox client and mailbox controller, through the
+ * provided void* of the common mailbox frameworks send- and receive APIs.
+ *
+ * This will be utilized when a mailbox controller is able to transmit
+ * more than a single word within a transmission, allowing the controller
+ * to fully utilize its available resources.
+ * No message protocol is enforced through this structure - a utilizing mailbox
+ * client driver shall implement its own message protocol, which may then be
+ * transmitted through an arm_mbox_msg.
+ *
+ * Given a message protocol of size A and an arm_mbox_msg containing data of
+ * length B, a mailbox channel may callback with B < A. In this case, the
+ * message protocol driver must implement a state machine which allows for
+ * multiple callbacks that provides parts of a full message of size A. This
+ * state machine must account for, that the length of the arm_mbox_msg received
+ * may vary between callbacks based on the underlying hardware as well as the
+ * transmitted data.
+ *
+ * Copyright (C) 2019 Arm Ltd.
+ */
+
+#ifndef _LINUX_ARM_MBOX_MESSAGE_H_
+#define _LINUX_ARM_MBOX_MESSAGE_H_
+
+#include <linux/types.h>
+
+struct arm_mbox_msg {
+       void *data;
+       size_t len;
+};
+
+#endif /* _LINUX_ARM_MBOX_MESSAGE_H_ */
-- 
2.17.1

Reply via email to