[4/4] mailbox: arm_mhuv2: add multi word transport protocol operations
diff mbox series

Message ID 20190717192616.1731-5-tushar.khandelwal@arm.com
State New
Headers show
Series
  • Arm MHUv2 Mailbox Controller Driver
Related show

Commit Message

tushar.khandelwal@arm.com July 17, 2019, 7:26 p.m. UTC
From: Morten Borup Petersen <morten.petersen@arm.com>

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 <morten.petersen@arm.com>
Signed-off-by: Tushar Khandelwal <tushar.khandelwal@arm.com>
Cc: jassisinghbrar@gmail.com
Cc: devicetree@vger.kernel.org
---
 drivers/mailbox/arm_mhu_v2.c | 225 +++++++++++++++++++++++++++++++++++
 1 file changed, 225 insertions(+)

Patch
diff mbox series

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;