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

@ -44,6 +44,9 @@ LOG_MODULE_REGISTER(npcx_i3c, CONFIG_I3C_LOG_LEVEL);
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
/* 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 */
/*
* Two ways to do read/write transfer .
* 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)) {
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);
NPCX_I3C_MCTRL_TYPE_I3C, false,
0);
if (ret < 0) {
LOG_ERR("%s: emit start of broadcast addr failed, error (%d)",
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;
}
} 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);

View file

@ -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