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>
This commit is contained in:
Alvis Sun 2024-05-08 11:20:04 +08:00 committed by Alberto Escolar
commit 03580e4a1b
2 changed files with 155 additions and 35 deletions

View file

@ -39,11 +39,14 @@ LOG_MODULE_REGISTER(npcx_i3c, CONFIG_I3C_LOG_LEVEL);
#define MCLKD_FREQ_45_MHZ MHZ(45) #define MCLKD_FREQ_45_MHZ MHZ(45)
#define I3C_STATUS_CLR_MASK \ #define I3C_STATUS_CLR_MASK \
(BIT(NPCX_I3C_MSTATUS_TGTSTART) | BIT(NPCX_I3C_MSTATUS_MCTRLDONE) | \ (BIT(NPCX_I3C_MSTATUS_TGTSTART) | BIT(NPCX_I3C_MSTATUS_MCTRLDONE) | \
BIT(NPCX_I3C_MSTATUS_COMPLETE) | BIT(NPCX_I3C_MSTATUS_IBIWON) | \ BIT(NPCX_I3C_MSTATUS_COMPLETE) | BIT(NPCX_I3C_MSTATUS_IBIWON) | \
BIT(NPCX_I3C_MSTATUS_NOWCNTLR)) 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 */ /* Supported I3C MCLKD frequency */
enum npcx_i3c_speed { enum npcx_i3c_speed {
NPCX_I3C_BUS_SPEED_45MHZ, 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) static bool npcx_i3c_has_error(struct i3c_reg *inst)
{ {
if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_ERRWARN)) { 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; 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] addr Dyamic address for xfer or 0x7E for CCC command.
* param[in] op_type Request type. * param[in] op_type Request type.
* param[in] is_read Read(true) or write(false) operation. * 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 * return 0, success
* else, error * else, error
@ -396,6 +400,56 @@ static inline int npcx_i3c_request_emit_stop(struct i3c_reg *inst)
return 0; 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) static inline int npcx_i3c_ibi_respond_nack(struct i3c_reg *inst)
{ {
uint32_t val = 0; uint32_t val = 0;
@ -668,6 +722,7 @@ out_wr_fifo_dma:
/* /*
* brief: Perform DMA read transaction. * 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) * 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. * 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. * brief: Perform one transfer transaction by DMA.
* (Support SDR and HDR-DDR)
* *
* param[in] inst Pointer to controller registers. * param[in] inst Pointer to controller registers.
* param[in] addr Target address. * 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, 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, 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; const struct npcx_i3c_config *config = dev->config;
struct i3c_reg *inst = config->base; struct i3c_reg *inst = config->base;
int ret = 0; 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_status_clear_all(inst);
npcx_i3c_errwarn_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 */ /* Emit START if needed */
if (emit_start) { 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) { if (ret != 0) {
LOG_ERR("%s: emit start fail", __func__); LOG_ERR("%s: emit start fail", __func__);
goto out_do_one_xfer_dma; 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: out_do_one_xfer_dma:
/* Emit STOP if needed */ /* Emit STOP or exit DDR if needed */
if (emit_stop) { if (emit_stop) {
npcx_i3c_request_emit_stop(inst); npcx_i3c_xfer_stop(inst);
} }
return ret; return ret;
@ -784,6 +867,7 @@ out_do_one_xfer_dma:
/* /*
* brief: Perform one transfer transaction. * brief: Perform one transfer transaction.
* (Support SDR only)
* *
* param[in] inst Pointer to controller registers. * param[in] inst Pointer to controller registers.
* param[in] addr Target address. * 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; const struct npcx_i3c_config *config = dev->config;
struct i3c_reg *inst = config->base; struct i3c_reg *inst = config->base;
struct npcx_i3c_data *data = dev->data;
uint32_t intmask; uint32_t intmask;
int xfered_len, ret = 0; int xfered_len, ret = 0;
bool send_broadcast = true; bool send_broadcast = true;
bool is_xfer_done = true; bool is_xfer_done = true;
enum npcx_i3c_mctrl_type op_type;
if (msgs == NULL) { if (msgs == NULL) {
return -EINVAL; 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 */ /* Iterate over all the messages */
for (int i = 0; i < num_msgs; i++) { for (int i = 0; i < num_msgs; i++) {
/* /*
* Check message is read or write operaion. * Check message is read or write operaion.
* For write operation, check the last data byte of a transmit message. * 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 #endif
/* /* Check message SDR or HDR mode */
* Two ways to do read/write transfer . bool is_msg_hdr = (msgs[i].flags & I3C_MSG_HDR) == I3C_MSG_HDR;
* 1. [S] + [0x7E] + [address] + [data] + [Sr or P]
* 2. [S] + [address] + [data] + [Sr or P] /* Set emit start type SDR or HDR-DDR mode */
* if (!is_msg_hdr || msgs[i].hdr_mode == 0) {
* Send broadcast header(0x7E) on first transfer or after a STOP, op_type = NPCX_I3C_MCTRL_TYPE_I3C; /* Set operation type SDR */
* unless flag is set not to.
*/ /*
if (!(msgs[i].flags & I3C_MSG_NBCH) && (send_broadcast)) { * SDR, send boradcast header(0x7E)
ret = npcx_i3c_request_emit_start(inst, I3C_BROADCAST_ADDR, *
NPCX_I3C_MCTRL_TYPE_I3C, false, 0); * Two ways to do read/write transfer (SDR mode).
if (ret < 0) { * 1. [S] + [0x7E] + [address] + [data] + [Sr or P]
LOG_ERR("%s: emit start of broadcast addr failed, error (%d)", * 2. [S] + [address] + [data] + [Sr or P]
__func__, ret); *
* 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; 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 #ifdef CONFIG_I3C_NPCX_DMA
/* Do transfer with target device */ /* Do transfer with target device */
xfered_len = npcx_i3c_do_one_xfer_dma(dev, target->dynamic_addr, xfered_len = npcx_i3c_do_one_xfer_dma(dev, target->dynamic_addr, op_type,
NPCX_I3C_MCTRL_TYPE_I3C, msgs[i].buf, msgs[i].buf, msgs[i].len, is_read, emit_start,
msgs[i].len, is_read, emit_start, emit_stop); emit_stop, msgs[i].hdr_cmd_code);
#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);
#endif #endif
if (xfered_len < 0) { if (xfered_len < 0) {
LOG_ERR("%s: do xfer fail", __func__); LOG_ERR("%s: do xfer fail", __func__);
ret = xfered_len; /* Set error code to ret */ 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; msgs[i].num_xfer = xfered_len;
if (emit_stop) { 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; 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 */ /* Emit stop if error occurs or stop flag not in the msg */
if ((ret != 0) || (is_xfer_done == false)) { if ((ret != 0) || (is_xfer_done == false)) {
npcx_i3c_request_emit_stop(inst); npcx_i3c_xfer_stop(inst);
} }
npcx_i3c_errwarn_clear_all(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 #ifdef CONFIG_I3C_USE_IBI
/* Target start detected */ /* Target start detected */
if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_TGTSTART)) { if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_TGTSTART)) {
LOG_DBG("ISR TGTSTART !");
/* Disable further target initiated IBI interrupt */ /* Disable further target initiated IBI interrupt */
inst->MINTCLR = BIT(NPCX_I3C_MINTCLR_TGTSTART); 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->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 */ ctrl_config->scl.i3c = config->clocks.i3c_pp_scl_hz; /* Set I3C frequency */
ret = npcx_i3c_configure(dev, I3C_CONFIG_CONTROLLER, ctrl_config); ret = npcx_i3c_configure(dev, I3C_CONFIG_CONTROLLER, ctrl_config);

View file

@ -1913,12 +1913,17 @@ struct i3c_reg {
#define MCTRL_IBIRESP_ACK_MANDATORY 2 /* ACK with mandatory byte */ #define MCTRL_IBIRESP_ACK_MANDATORY 2 /* ACK with mandatory byte */
#define MCTRL_IBIRESP_MANUAL 3 #define MCTRL_IBIRESP_MANUAL 3
/* For REQUEST = EmitStartAddr */
enum npcx_i3c_mctrl_type { enum npcx_i3c_mctrl_type {
NPCX_I3C_MCTRL_TYPE_I3C, NPCX_I3C_MCTRL_TYPE_I3C,
NPCX_I3C_MCTRL_TYPE_I2C, NPCX_I3C_MCTRL_TYPE_I2C,
NPCX_I3C_MCTRL_TYPE_I3C_HDR_DDR, 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 */ /* MSTATUS options */
#define MSTATUS_STATE_IDLE 0x0 #define MSTATUS_STATE_IDLE 0x0
#define MSTATUS_STATE_TGTREQ 0x1 #define MSTATUS_STATE_TGTREQ 0x1