drivers: i3c: npcx: add HDR-DDR mode for transfer

1. Support HDR-DDR DMA transfer.
2. Remove polling mode in transfer.

Signed-off-by: Alvis Sun <yfsun@nuvoton.com>
diff --git a/drivers/i3c/i3c_npcx.c b/drivers/i3c/i3c_npcx.c
index 9790e59..75b9c73 100644
--- a/drivers/i3c/i3c_npcx.c
+++ b/drivers/i3c/i3c_npcx.c
@@ -39,11 +39,14 @@
 
 #define MCLKD_FREQ_45_MHZ MHZ(45)
 
-#define I3C_STATUS_CLR_MASK                                                 \
-	(BIT(NPCX_I3C_MSTATUS_TGTSTART) | BIT(NPCX_I3C_MSTATUS_MCTRLDONE) | \
-	 BIT(NPCX_I3C_MSTATUS_COMPLETE) | BIT(NPCX_I3C_MSTATUS_IBIWON) |    \
+#define I3C_STATUS_CLR_MASK                                                                        \
+	(BIT(NPCX_I3C_MSTATUS_TGTSTART) | BIT(NPCX_I3C_MSTATUS_MCTRLDONE) |                        \
+	 BIT(NPCX_I3C_MSTATUS_COMPLETE) | BIT(NPCX_I3C_MSTATUS_IBIWON) |                           \
 	 BIT(NPCX_I3C_MSTATUS_NOWCNTLR))
 
+#define HDR_DDR_CMD_AND_CRC_SZ_WORD 0x2 /* 2 words =  Command(1 word) + CRC(1 word) */
+#define HDR_RD_CMD                  0x80
+
 /* Supported I3C MCLKD frequency */
 enum npcx_i3c_speed {
 	NPCX_I3C_BUS_SPEED_45MHZ,
@@ -217,7 +220,7 @@
 static bool npcx_i3c_has_error(struct i3c_reg *inst)
 {
 	if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_ERRWARN)) {
-		LOG_WRN("ERROR: MSTATUS 0x%08x MERRWARN 0x%08x", inst->MSTATUS, inst->MERRWARN);
+		LOG_ERR("ERROR: MSTATUS 0x%08x MERRWARN 0x%08x", inst->MSTATUS, inst->MERRWARN);
 
 		return true;
 	}
@@ -314,7 +317,8 @@
  * param[in] addr     Dyamic address for xfer or 0x7E for CCC command.
  * param[in] op_type  Request type.
  * param[in] is_read  Read(true) or write(false) operation.
- * param[in] read_sz  Read size.
+ * param[in] read_sz  Read size in bytes.
+ *                    If op_tye is HDR-DDR, the read_sz must be the number of words.
  *
  * return  0, success
  *         else, error
@@ -396,6 +400,56 @@
 	return 0;
 }
 
+static inline int npcx_i3c_request_hdr_exit(struct i3c_reg *inst)
+{
+	uint32_t val = 0;
+	uint32_t state;
+	int ret;
+
+	/* Before sending the HDR exit command, check the HDR mode */
+	state = npcx_i3c_state_get(inst);
+	if (state != MSTATUS_STATE_MSGDDR) {
+		LOG_ERR("%s, state error: %#x", __func__, state);
+		return -EPERM;
+	}
+
+	SET_FIELD(val, NPCX_I3C_MCTRL_TYPE, MCTRL_TYPE_HDR_EXIT);
+	SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_FORCEEXIT);
+
+	ret = npcx_i3c_send_request(inst, val);
+	if (ret != 0) {
+		LOG_ERR("Request hdr exit error %d", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static inline int npcx_i3c_xfer_stop(struct i3c_reg *inst)
+{
+	uint32_t state;
+	int ret;
+
+	state = npcx_i3c_state_get(inst);
+	LOG_DBG("%s, state=%d", __func__, state);
+
+	switch (state) {
+	case MSTATUS_STATE_NORMACT: /* SDR */
+		ret = npcx_i3c_request_emit_stop(inst);
+		break;
+	case MSTATUS_STATE_MSGDDR:  /* HDR-DDR */
+		ret = npcx_i3c_request_hdr_exit(inst);
+		break;
+	default:
+		/* Not supported */
+		ret = -ENOTSUP;
+		LOG_WRN("xfer_stop state not supported, state:%d", state);
+		break;
+	}
+
+	return ret;
+}
+
 static inline int npcx_i3c_ibi_respond_nack(struct i3c_reg *inst)
 {
 	uint32_t val = 0;
@@ -668,6 +722,7 @@
 
 /*
  * brief:  Perform DMA read transaction.
+ *         (Data width used for DMA transfers is "byte")
  *
  * For read end, use the MDMA end-of-transfer interrupt(SIEN bit)
  * instead of using the I3CI interrupt generated by COMPLETE bit in MSTATUS register.
@@ -717,6 +772,7 @@
 
 /*
  * brief:  Perform one transfer transaction by DMA.
+ *         (Support SDR and HDR-DDR)
  *
  * param[in] inst        Pointer to controller registers.
  * param[in] addr        Target address.
@@ -731,18 +787,45 @@
  */
 static int npcx_i3c_do_one_xfer_dma(const struct device *dev, uint8_t addr,
 				    enum npcx_i3c_mctrl_type op_type, uint8_t *buf, size_t buf_sz,
-				    bool is_read, bool emit_start, bool emit_stop)
+				    bool is_read, bool emit_start, bool emit_stop, uint8_t hdr_cmd)
 {
 	const struct npcx_i3c_config *config = dev->config;
 	struct i3c_reg *inst = config->base;
 	int ret = 0;
+	bool is_hdr_ddr = (op_type == NPCX_I3C_MCTRL_TYPE_I3C_HDR_DDR) ? true : false;
+	size_t rd_len = buf_sz;
 
 	npcx_i3c_status_clear_all(inst);
 	npcx_i3c_errwarn_clear_all(inst);
 
+	/* Check HDR-DDR moves data by words */
+	if (is_hdr_ddr && (buf_sz % 2 != 0)) {
+		LOG_ERR("%s, HDR-DDR data length should be even, len=%#x", __func__, buf_sz);
+		return -EINVAL;
+	}
+
 	/* Emit START if needed */
 	if (emit_start) {
-		ret = npcx_i3c_request_emit_start(inst, addr, op_type, is_read, buf_sz);
+		/*
+		 * For HDR-DDR mode read, RDTERM also includes one word (16 bits) for CRC.
+		 * For example, to read 8 bytes, set RDTERM to 6.
+		 * (1 word HDR-DDR command + 4 words data + 1 word for CRC)
+		 */
+		if (is_hdr_ddr) {
+			if (is_read) {
+				/* The unit of rd_len is "word" in DDR mode */
+				rd_len /= sizeof(uint16_t);    /* byte to word */
+				rd_len += HDR_DDR_CMD_AND_CRC_SZ_WORD;
+				hdr_cmd |= HDR_RD_CMD;
+			} else {
+				hdr_cmd &= ~HDR_RD_CMD;
+			}
+
+			/* Write the command code for the HDR-DDR message */
+			inst->MWDATAB = hdr_cmd;
+		}
+
+		ret = npcx_i3c_request_emit_start(inst, addr, op_type, is_read, rd_len);
 		if (ret != 0) {
 			LOG_ERR("%s: emit start fail", __func__);
 			goto out_do_one_xfer_dma;
@@ -773,9 +856,9 @@
 	}
 
 out_do_one_xfer_dma:
-	/* Emit STOP if needed */
+	/* Emit STOP or exit DDR if needed */
 	if (emit_stop) {
-		npcx_i3c_request_emit_stop(inst);
+		npcx_i3c_xfer_stop(inst);
 	}
 
 	return ret;
@@ -784,6 +867,7 @@
 
 /*
  * brief:  Perform one transfer transaction.
+ *         (Support SDR only)
  *
  * param[in] inst        Pointer to controller registers.
  * param[in] addr        Target address.
@@ -882,10 +966,12 @@
 {
 	const struct npcx_i3c_config *config = dev->config;
 	struct i3c_reg *inst = config->base;
+	struct npcx_i3c_data *data = dev->data;
 	uint32_t intmask;
 	int xfered_len, ret = 0;
 	bool send_broadcast = true;
 	bool is_xfer_done = true;
+	enum npcx_i3c_mctrl_type op_type;
 
 	if (msgs == NULL) {
 		return -EINVAL;
@@ -913,7 +999,6 @@
 
 	/* Iterate over all the messages */
 	for (int i = 0; i < num_msgs; i++) {
-
 		/*
 		 * Check message is read or write operaion.
 		 * For write operation, check the last data byte of a transmit message.
@@ -958,35 +1043,63 @@
 		}
 #endif
 
-		/*
-		 * Two ways to do read/write transfer .
-		 * 1. [S] + [0x7E]    + [address] + [data] + [Sr or P]
-		 * 2. [S] + [address] + [data]    + [Sr or P]
-		 *
-		 * Send broadcast header(0x7E) on first transfer or after a STOP,
-		 * unless flag is set not to.
-		 */
-		if (!(msgs[i].flags & I3C_MSG_NBCH) && (send_broadcast)) {
-			ret = npcx_i3c_request_emit_start(inst, I3C_BROADCAST_ADDR,
-							  NPCX_I3C_MCTRL_TYPE_I3C, false, 0);
-			if (ret < 0) {
-				LOG_ERR("%s: emit start of broadcast addr failed, error (%d)",
-					__func__, ret);
+		/* Check message SDR or HDR mode */
+		bool is_msg_hdr = (msgs[i].flags & I3C_MSG_HDR) == I3C_MSG_HDR;
+
+		/* Set emit start type SDR or HDR-DDR mode */
+		if (!is_msg_hdr || msgs[i].hdr_mode == 0) {
+			op_type = NPCX_I3C_MCTRL_TYPE_I3C; /* Set operation type SDR */
+
+			/*
+			 * SDR, send boradcast header(0x7E)
+			 *
+			 * Two ways to do read/write transfer (SDR mode).
+			 * 1. [S] + [0x7E]    + [address] + [data] + [Sr or P]
+			 * 2. [S] + [address] + [data]    + [Sr or P]
+			 *
+			 * Send broadcast header(0x7E) on first transfer or after a STOP,
+			 * unless flag is set not to.
+			 */
+			if (!(msgs[i].flags & I3C_MSG_NBCH) && send_broadcast) {
+				ret = npcx_i3c_request_emit_start(inst, I3C_BROADCAST_ADDR,
+								  NPCX_I3C_MCTRL_TYPE_I3C, false,
+								  0);
+				if (ret < 0) {
+					LOG_ERR("%s: emit start of broadcast addr failed, error "
+						"(%d)",
+						__func__, ret);
+					break;
+				}
+				send_broadcast = false;
+			}
+		} else if ((data->common.ctrl_config.supported_hdr & I3C_MSG_HDR_DDR) &&
+			   (msgs[i].hdr_mode == I3C_MSG_HDR_DDR) && is_msg_hdr) {
+
+			op_type = NPCX_I3C_MCTRL_TYPE_I3C_HDR_DDR; /* Set operation type DDR */
+
+			/* Check HDR-DDR moves data by words */
+			if ((msgs[i].len % 2) != 0x0) {
+				LOG_ERR("HDR-DDR data length should be number of words , xfer "
+					"len=%d", msgs[i].num_xfer);
+				ret = -EINVAL;
 				break;
 			}
-			send_broadcast = false;
+		} else {
+			LOG_ERR("%s: %s controller HDR Mode %#x\r\n"
+				"msg HDR mode %#x, msg flag %#x",
+				__func__, dev->name, data->common.ctrl_config.supported_hdr,
+				msgs[i].hdr_mode, msgs[i].flags);
+			ret = -ENOTSUP;
+			break;
 		}
 
 #ifdef CONFIG_I3C_NPCX_DMA
 		/* Do transfer with target device */
-		xfered_len = npcx_i3c_do_one_xfer_dma(dev, target->dynamic_addr,
-						      NPCX_I3C_MCTRL_TYPE_I3C, msgs[i].buf,
-						      msgs[i].len, is_read, emit_start, emit_stop);
-#else
-		xfered_len = npcx_i3c_do_one_xfer(inst, target->dynamic_addr,
-						  NPCX_I3C_MCTRL_TYPE_I3C, msgs[i].buf, msgs[i].len,
-						  is_read, emit_start, emit_stop, no_ending);
+		xfered_len = npcx_i3c_do_one_xfer_dma(dev, target->dynamic_addr, op_type,
+						      msgs[i].buf, msgs[i].len, is_read, emit_start,
+						      emit_stop, msgs[i].hdr_cmd_code);
 #endif
+
 		if (xfered_len < 0) {
 			LOG_ERR("%s: do xfer fail", __func__);
 			ret = xfered_len; /* Set error code to ret */
@@ -997,7 +1110,7 @@
 		msgs[i].num_xfer = xfered_len;
 
 		if (emit_stop) {
-			/* After a STOP, send broadcast header before next msg */
+			/* SDR. After a STOP, send broadcast header before next msg */
 			send_broadcast = true;
 		}
 
@@ -1009,7 +1122,7 @@
 
 	/* Emit stop if error occurs or stop flag not in the msg */
 	if ((ret != 0) || (is_xfer_done == false)) {
-		npcx_i3c_request_emit_stop(inst);
+		npcx_i3c_xfer_stop(inst);
 	}
 
 	npcx_i3c_errwarn_clear_all(inst);
@@ -1681,6 +1794,8 @@
 #ifdef CONFIG_I3C_USE_IBI
 	/* Target start detected */
 	if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_TGTSTART)) {
+		LOG_DBG("ISR TGTSTART !");
+
 		/* Disable further target initiated IBI interrupt */
 		inst->MINTCLR = BIT(NPCX_I3C_MINTCLR_TGTSTART);
 
@@ -1958,7 +2073,7 @@
 	}
 
 	ctrl_config->is_secondary = false; /* Currently can only act as primary controller. */
-	ctrl_config->supported_hdr = 0U;   /* HDR mode not supported at the moment. */
+	ctrl_config->supported_hdr = I3C_MSG_HDR_DDR; /* HDR-DDR mode is supported. */
 	ctrl_config->scl.i3c = config->clocks.i3c_pp_scl_hz; /* Set I3C frequency */
 
 	ret = npcx_i3c_configure(dev, I3C_CONFIG_CONTROLLER, ctrl_config);
diff --git a/soc/nuvoton/npcx/common/reg/reg_def.h b/soc/nuvoton/npcx/common/reg/reg_def.h
index ae02a6e..b43c3d5 100644
--- a/soc/nuvoton/npcx/common/reg/reg_def.h
+++ b/soc/nuvoton/npcx/common/reg/reg_def.h
@@ -1913,12 +1913,17 @@
 #define MCTRL_IBIRESP_ACK_MANDATORY 2 /* ACK with mandatory byte  */
 #define MCTRL_IBIRESP_MANUAL        3
 
+/* For REQUEST = EmitStartAddr */
 enum npcx_i3c_mctrl_type {
 	NPCX_I3C_MCTRL_TYPE_I3C,
 	NPCX_I3C_MCTRL_TYPE_I2C,
 	NPCX_I3C_MCTRL_TYPE_I3C_HDR_DDR,
 };
 
+/* For REQUEST = ForceExit/Target Reset */
+#define MCTRL_TYPE_HDR_EXIT    0
+#define MCTRL_TYPE_TGT_RESTART 2
+
 /* MSTATUS options */
 #define MSTATUS_STATE_IDLE    0x0
 #define MSTATUS_STATE_TGTREQ  0x1