From: Morten Borup Petersen <[email protected]>

When in multi-word mode, the mailbox controller will provide a single
mailbox. It is required that the MHU device has at least 2 channel windows
available for the MHU to function in multi-word mode.

Transmitting and receiving data through the mailbox framework in
multi-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/arm_mhu_v2.c | 225 +++++++++++++++++++++++++++++++++++
 1 file changed, 225 insertions(+)

diff --git a/drivers/mailbox/arm_mhu_v2.c b/drivers/mailbox/arm_mhu_v2.c
index 0e3fa5917925..324b19bdb28a 100644
--- a/drivers/mailbox/arm_mhu_v2.c
+++ b/drivers/mailbox/arm_mhu_v2.c
@@ -430,6 +430,228 @@ static const struct mhuv2_ops mhuv2_single_word_ops = {
 };
 /* ========================================================================== 
*/
 
+/* ================ Multi word transport protocol operations ================ 
*/
+static inline int mhuv2_read_data_multi_word(struct arm_mhuv2 *mhuv2,
+                                            struct mbox_chan *chan,
+                                            struct arm_mbox_msg *msg)
+{
+       int ch;
+       const int channels =
+               readl_relaxed_bitfield(&mhuv2->reg.recv->MHU_CFG, NUM_CH);
+
+       msg->data = kzalloc(MHUV2_STAT_BYTES * channels, GFP_KERNEL);
+
+       for (ch = 0; ch < channels; ch++) {
+               /*
+                * Messages are expected to be received in order of most
+                * significant word to least significant word.
+                * (see mhuv2_send_data_multi_word)
+                */
+               const mhuv2_stat_reg_t word =
+                       readl_relaxed(&mhuv2->reg.recv->channel[ch].STAT);
+               ((mhuv2_stat_reg_t *)msg->data)[channels - 1 - ch] = word;
+       }
+
+       msg->len = channels * MHUV2_STAT_BYTES;
+       return 0;
+}
+
+static inline int mhuv2_clear_data_multi_word(struct arm_mhuv2 *mhuv2,
+                                             struct mbox_chan *chan,
+                                             struct arm_mbox_msg *msg)
+{
+       int ch;
+       const int channels =
+               readl_relaxed_bitfield(&mhuv2->reg.recv->MHU_CFG, NUM_CH);
+
+       for (ch = 0; ch < channels; ch++) {
+               /*
+                * Last channel window must be cleared as the final operation.
+                * Upon clearing the last channel window register, which is
+                * unmasked in multi-word mode, the interrupt is deasserted.
+                */
+               writel_relaxed(
+                       readl_relaxed(&mhuv2->reg.recv->channel[ch].STAT),
+                       &mhuv2->reg.recv->channel[ch].STAT_CLEAR);
+       }
+       return 0;
+}
+
+static inline int __mhuv2_mw_bytes_to_send(const int bytes_in_round,
+                                           const int bytes_left)
+{
+       /*
+        * Bytes to send on the current channel will always be MHUV2_STAT_BYTES
+        * unless in the last round and
+        *      msg->len % MHUV2_STAT_BYTES != 0
+        */
+       if (bytes_in_round % MHUV2_STAT_BYTES != 0) {
+               const int bts = bytes_left % MHUV2_STAT_BYTES;
+               return bts == 0 ? MHUV2_STAT_BYTES : bts;
+       } else {
+               return MHUV2_STAT_BYTES;
+       }
+}
+
+static inline int mhuv2_send_data_multi_word(struct arm_mhuv2 *mhuv2,
+                                            struct mbox_chan *chan,
+                                            const struct arm_mbox_msg *msg)
+{
+       /*
+        * Message will be transmitted from most significant to least
+        * significant word. This is to allow for messages shorter than
+        * $channels to still trigger the receiver interrupt which gets
+        * activated when the last STAT register is written. As an example, a
+        * 6-word message is to be written on a 4-channel MHU connection:
+        * Registers marked with '*' are masked, and will not generate an
+        * interrupt on the receiver side once written.
+        *
+        * uint32_t *data = [0x00000001],[0x00000002],[0x00000003],[0x00000004],
+        *                  [0x00000005], [0x00000006]
+        *
+        *  ROUND 1:
+        *   STAT reg      To write    Write sequence
+        *  [ STAT 3 ] <- [0x00000001]       4 <- triggers interrupt on receiver
+        * *[ STAT 2 ] <- [0x00000002]       3
+        * *[ STAT 1 ] <- [0x00000003]       2
+        * *[ STAT 0 ] <- [0x00000004]       1
+        *
+        *  data += 4 // Increment data pointer by number of STAT regs
+        *
+        *  ROUND 2:
+        *   STAT reg      To write    Write sequence
+        *  [ STAT 3 ] <- [0x00000005]       2 <- triggers interrupt on receiver
+        * *[ STAT 2 ] <- [0x00000006]       1
+        * *[ STAT 1 ] <- [0x00000000]
+        * *[ STAT 0 ] <- [0x00000000]
+        */
+       int bytes_left, bytes_to_send, i, ch_idx;
+       const int ch_windows =
+               readl_relaxed_bitfield(&mhuv2->reg.recv->MHU_CFG, NUM_CH);
+       const size_t round_capacity = ch_windows * MHUV2_STAT_BYTES;
+
+       bytes_left = msg->len;
+       mhuv2_stat_reg_t *data = msg->data;
+
+       while (bytes_left > 0) {
+               /* Note: Each entry of this loop indicates a new ROUND */
+               if (*(u32 *)data == 0) {
+                       dev_err(mhuv2->dev,
+                               "values in *data aligned on NUM_STAT boundaries 
must not be zero to ensure that receiver interrupt is triggered\n",
+                               ch_windows);
+                       return -EINVAL;
+               }
+
+               const int bytes_in_round = bytes_left > round_capacity ?
+                                                  round_capacity :
+                                                  bytes_left;
+
+               for (i = (ch_windows - 1); i >= 0; i--) {
+                       ch_idx = ch_windows - 1 - i;
+                       /*
+                        * Check whether data should be transmitted in register
+                        * of index 'ch'.
+                        */
+                       if (bytes_in_round > (i * MHUV2_STAT_BYTES)) {
+                               mhuv2_stat_reg_t word = data[i];
+
+                               bytes_to_send = __mhuv2_mw_bytes_to_send(
+                                       bytes_in_round, bytes_left);
+
+                               if (bytes_to_send != MHUV2_STAT_BYTES) {
+                                       word &= LSB_MASK(bytes_to_send *
+                                                        __CHAR_BIT__);
+                               }
+                               while (readl_relaxed(
+                                              &mhuv2->reg.send->channel[ch_idx]
+                                                       .STAT) != 0)
+                                       continue;
+
+                               writel_relaxed(
+                                   word,
+                                   &mhuv2->reg.send->channel[ch_idx].STAT_SET);
+                               bytes_left -= bytes_to_send;
+                       }
+               }
+
+               data += ch_windows;
+
+               for (ch_idx = 0; ch_idx < ch_windows; ch_idx++) {
+                       while (readl_relaxed(
+                                  &mhuv2->reg.send->channel[ch_idx].STAT) != 0)
+                               continue;
+               }
+       }
+       return 0;
+}
+
+
+static inline int mhuv2_last_tx_done_multi_word(struct arm_mhuv2 *mhuv2,
+                                               struct mbox_chan *chan)
+{
+       int ch_idx;
+       bool tx_done = true;
+
+       for (ch_idx = 0;
+            ch_idx < readl_relaxed_bitfield(&mhuv2->reg.send->MHU_CFG, NUM_CH);
+            ch_idx++) {
+               tx_done &= readl_relaxed(
+                                  &mhuv2->reg.send->channel[ch_idx].STAT) == 0;
+       }
+       return tx_done;
+}
+
+static inline int mhuv2_setup_multi_word(struct arm_mhuv2 *mhuv2)
+{
+       int ret, i;
+
+       const u32 channel_windows =
+               readl_relaxed_bitfield(mhuv2->frame == RECEIVER_FRAME ?
+                                              &mhuv2->reg.recv->MHU_CFG :
+                                              &mhuv2->reg.send->MHU_CFG,
+                                      NUM_CH);
+       if (channel_windows < 2) {
+               dev_err(mhuv2->dev,
+                       "Error: at least 2 MHU channel windows are required for 
using the multi-word transfer protocol");
+               return -ENODEV;
+       }
+
+       if (mhuv2->frame == RECEIVER_FRAME) {
+               /*
+                * The multi-word transport protocol mandates that all but
+                * the last status register must be masked.
+                */
+               for (i = 0; i < (channel_windows - 1); i++) {
+                       writel_relaxed(-1,
+                                      &mhuv2->reg.recv->channel[i].MASK_SET);
+               }
+       }
+
+       mhuv2->mbox.num_chans = 1;
+       mhuv2->mbox.chans =
+               devm_kzalloc(mhuv2->dev,
+                            mhuv2->mbox.num_chans * sizeof(struct mbox_chan),
+                            GFP_KERNEL);
+
+       return 0;
+}
+
+static inline struct mbox_chan *
+       mhuv2_get_active_mbox_chan_multi_word(struct arm_mhuv2 *mhuv2)
+{
+       return &mhuv2->mbox.chans[0];
+}
+
+static const struct mhuv2_ops mhuv2_multi_word_ops = {
+       .read_data = mhuv2_read_data_multi_word,
+       .clear_data = mhuv2_clear_data_multi_word,
+       .send_data = mhuv2_send_data_multi_word,
+       .setup = mhuv2_setup_multi_word,
+       .last_tx_done = mhuv2_last_tx_done_multi_word,
+       .get_active_mbox_chan = mhuv2_get_active_mbox_chan_multi_word,
+};
+/* ========================================================================== 
*/
+
 /* =================== Doorbell transport protocol operations =============== 
*/
 
 static inline int mhuv2_read_data_doorbell(struct arm_mhuv2 *mhuv2,
@@ -740,6 +962,9 @@ static int mhuv2_probe(struct amba_device *adev, const 
struct amba_id *id)
 
        /* Assign transport protocol-specific operations */
        switch (mhuv2->protocol) {
+       case MULTI_WORD:
+               mhuv2->ops = &mhuv2_multi_word_ops;
+               break;
        case SINGLE_WORD:
                mhuv2->ops = &mhuv2_single_word_ops;
                break;
-- 
2.17.1

Reply via email to