diff --git a/drivers/i3c/i3c_npcx.c b/drivers/i3c/i3c_npcx.c index 9790e593915..75b9c73c966 100644 --- a/drivers/i3c/i3c_npcx.c +++ b/drivers/i3c/i3c_npcx.c @@ -39,11 +39,14 @@ LOG_MODULE_REGISTER(npcx_i3c, CONFIG_I3C_LOG_LEVEL); #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 inline void npcx_i3c_interrupt_enable(struct i3c_reg *inst, uint32_t mask 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 @@ static inline int npcx_i3c_request_auto_ibi(struct i3c_reg *inst) * 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 @@ static inline int npcx_i3c_request_emit_stop(struct i3c_reg *inst) 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 @@ out_wr_fifo_dma: /* * 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 @@ static int npcx_i3c_xfer_read_fifo_dma(const struct device *dev, uint8_t *buf, u /* * 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_xfer_read_fifo_dma(const struct device *dev, uint8_t *buf, u */ 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 @@ static int npcx_i3c_do_one_xfer_dma(const struct device *dev, uint8_t addr, } 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 @@ out_do_one_xfer_dma: /* * brief: Perform one transfer transaction. + * (Support SDR only) * * param[in] inst Pointer to controller registers. * param[in] addr Target address. @@ -882,10 +966,12 @@ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *t { 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 @@ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *t /* 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 @@ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *t } #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 @@ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *t 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 @@ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *t /* 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 @@ static void npcx_i3c_isr(const struct device *dev) #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 @@ static int npcx_i3c_init(const struct device *dev) } 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 ae02a6e506d..b43c3d54bc1 100644 --- a/soc/nuvoton/npcx/common/reg/reg_def.h +++ b/soc/nuvoton/npcx/common/reg/reg_def.h @@ -1913,12 +1913,17 @@ struct i3c_reg { #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