/* * Copyright (c) 2024 Nuvoton Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ /*132*/ #define DT_DRV_COMPAT nuvoton_npcx_i3c #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(npcx_i3c, CONFIG_I3C_LOG_LEVEL); /* MCONFIG register options */ #define MCONFIG_CTRENA_OFF 0x0 #define MCONFIG_CTRENA_ON 0x1 #define MCONFIG_CTRENA_CAPABLE 0x2 #define MCONFIG_HKEEP_EXT_SDA_SCL 0x3 /* MCTRL register options */ #define MCTRL_REQUEST_NONE 0 /* None */ #define MCTRL_REQUEST_EMITSTARTADDR 1 /* Emit a START */ #define MCTRL_REQUEST_EMITSTOP 2 /* Emit a STOP */ #define MCTRL_REQUEST_IBIACKNACK 3 /* Manually ACK or NACK an IBI */ #define MCTRL_REQUEST_PROCESSDAA 4 /* Starts the DAA process */ #define MCTRL_REQUEST_FORCEEXIT 6 /* Emit HDR Exit Pattern */ /* Emits a START with address 7Eh when a slave pulls I3C_SDA low to request an IBI */ #define MCTRL_REQUEST_AUTOIBI 7 /* ACK with mandatory byte determined by IBIRULES or ACK with no mandatory byte */ #define MCTRL_IBIRESP_ACK 0 #define MCTRL_IBIRESP_NACK 1 /* NACK */ #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 register options */ #define MSTATUS_STATE_IDLE 0x0 #define MSTATUS_STATE_TGTREQ 0x1 #define MSTATUS_STATE_NORMACT 0x3 /* SDR message mode */ #define MSTATUS_STATE_MSGDDR 0x4 #define MSTATUS_STATE_DAA 0x5 #define MSTATUS_STATE_IBIACK 0x6 #define MSTATUS_STATE_IBIRCV 0x7 #define MSTATUS_IBITYPE_NONE 0x0 #define MSTATUS_IBITYPE_IBI 0x1 #define MSTATUS_IBITYPE_CR 0x2 #define MSTATUS_IBITYPE_HJ 0x3 /* IBIRULES register options */ #define IBIRULES_ADDR_MSK 0x3F #define IBIRULES_ADDR_SHIFT 0x6 /* MDMACTRL register options */ #define MDMA_DMAFB_DISABLE 0x0 #define MDMA_DMAFB_EN_ONE_FRAME 0x1 #define MDMA_DMAFB_EN_MANUAL 0x2 #define MDMA_DMATB_DISABLE 0x0 #define MDMA_DMATB_EN_ONE_FRAME 0x1 #define MDMA_DMATB_EN_MANUAL 0x2 /* CONFIG register options */ #define CFG_HDRCMD_RD_FROM_FIFIO 0 /* CTRL register options */ #define CTRL_EVENT_NORMAL 0 #define CTRL_EVENT_IBI 1 #define CTRL_EVENT_CNTLR_REQ 2 #define CTRL_EVENT_HJ 3 /* STATUS register options */ #define STATUS_EVDET_NONE 0 #define STATUS_EVDET_REQ_NOT_SENT 1 #define STATUS_EVDET_REQ_SENT_NACKED 2 #define STATUS_EVDET_REQ_SENT_ACKED 3 /* Local Constants Definition */ #define NPCX_I3C_CHK_TIMEOUT_US 10000 /* Timeout for checking register status */ #define I3C_SCL_PP_FREQ_MAX_MHZ 12500000 #define I3C_SCL_OD_FREQ_MAX_MHZ 4170000 #define I3C_BUS_TLOW_PP_MIN_NS 24 /* T_LOW period in push-pull mode */ #define I3C_BUS_THigh_PP_MIN_NS 24 /* T_High period in push-pull mode */ #define I3C_BUS_TLOW_OD_MIN_NS 200 /* T_LOW period in open-drain mode */ #define PPBAUD_DIV_MAX (BIT(GET_FIELD_SZ(NPCX_I3C_MCONFIG_PPBAUD)) - 1) /* PPBAUD divider max */ #define I3C_BUS_I2C_BAUD_RATE_FAST_MODE 0x0D #define I3C_BUS_I2C_BAUD_RATE_FAST_MODE_PLUS 0x03 #define DAA_TGT_INFO_SZ 0x8 /* 8 bytes = PID(6) + BCR(1) + DCR(1) */ #define BAMATCH_DIV 0x4 /* BAMATCH = APB4_CLK divided by four */ /* Default maximum time we allow for an I3C transfer */ #define I3C_TRANS_TIMEOUT_MS K_MSEC(100) #define MCLKD_FREQ_MHZ(freq) MHZ(freq) #define I3C_STATUS_CLR_MASK \ (BIT(NPCX_I3C_MSTATUS_MCTRLDONE) | BIT(NPCX_I3C_MSTATUS_COMPLETE) | \ BIT(NPCX_I3C_MSTATUS_IBIWON) | BIT(NPCX_I3C_MSTATUS_NOWCNTLR)) #define I3C_TGT_INTSET_MASK \ (BIT(NPCX_I3C_INTSET_START) | BIT(NPCX_I3C_INTSET_MATCHED) | BIT(NPCX_I3C_INTSET_STOP) | \ BIT(NPCX_I3C_INTSET_DACHG) | BIT(NPCX_I3C_INTSET_CCC) | BIT(NPCX_I3C_INTSET_ERRWARN) | \ BIT(NPCX_I3C_INTSET_HDRMATCH) | BIT(NPCX_I3C_INTSET_CHANDLED) | \ BIT(NPCX_I3C_INTSET_EVENT)) #define HDR_DDR_CMD_AND_CRC_SZ_WORD 0x2 /* 2 words = Command(1 word) + CRC(1 word) */ #define HDR_RD_CMD 0x80 /* I3C moudle and port parsing from instance_id */ #define GET_MODULE_ID(inst_id) ((inst_id & 0xf0) >> 4) #define GET_PORT_ID(inst_id) (inst_id & 0xf) /* I3C target PID parsing */ #define GET_PID_VENDOR_ID(pid) (((uint64_t)pid >> 33) & 0x7fff) /* PID[47:33] */ #define GET_PID_ID_TYP(pid) (((uint64_t)pid >> 32) & 0x1) /* PID[32] */ #define GET_PID_PARTNO(pid) (pid & 0xffffffff) /* PID[31:0] */ #define I3C_TGT_WR_REQ_WAIT_US 10 /* I3C target write request MDMA completion after stop */ /* Supported I3C MCLKD frequency */ enum npcx_i3c_speed { NPCX_I3C_BUS_SPEED_40MHZ, NPCX_I3C_BUS_SPEED_45MHZ, NPCX_I3C_BUS_SPEED_48MHZ, NPCX_I3C_BUS_SPEED_50MHZ, }; /* Operation type */ enum npcx_i3c_oper_state { NPCX_I3C_OP_STATE_IDLE, NPCX_I3C_OP_STATE_WR, NPCX_I3C_OP_STATE_RD, NPCX_I3C_OP_STATE_IBI, NPCX_I3C_OP_STATE_MAX, }; /* I3C timing configuration for each i3c speed */ struct npcx_i3c_timing_cfg { uint8_t ppbaud; /* Push-Pull high period */ uint8_t pplow; /* Push-Pull low period */ uint8_t odhpp; /* Open-Drain high period */ uint8_t odbaud; /* Open-Drain low period */ }; /* Recommended I3C timing values are based on different MCLKD frequency */ static const struct npcx_i3c_timing_cfg npcx_def_speed_cfg[] = { /* PP = 12.5 mhz, OD = 4.17 Mhz */ [NPCX_I3C_BUS_SPEED_40MHZ] = {.ppbaud = 1, .pplow = 0, .odhpp = 1, .odbaud = 3}, [NPCX_I3C_BUS_SPEED_45MHZ] = {.ppbaud = 1, .pplow = 0, .odhpp = 1, .odbaud = 4}, [NPCX_I3C_BUS_SPEED_48MHZ] = {.ppbaud = 1, .pplow = 0, .odhpp = 1, .odbaud = 4}, [NPCX_I3C_BUS_SPEED_50MHZ] = {.ppbaud = 1, .pplow = 0, .odhpp = 1, .odbaud = 4}, }; struct npcx_i3c_config { /* Common I3C Driver Config */ struct i3c_driver_config common; /* Pointer to controller registers. */ struct i3c_reg *base; /* Pointer to the clock device. */ const struct device *clock_dev; /* Reset controller */ struct reset_dt_spec reset; /* Clock control subsys related struct. */ struct npcx_clk_cfg clock_subsys; /* Reference clock to determine 1 μs bus available time */ struct npcx_clk_cfg ref_clk_subsys; /* Pointer to pin control device. */ const struct pinctrl_dev_config *pincfg; /* Interrupt configuration function. */ void (*irq_config_func)(const struct device *dev); uint8_t instance_id; /* bit[8:4] module id, bit[3:0] port id */ /* I3C clock frequency configuration */ struct { uint32_t i3c_pp_scl_hz; /* I3C push pull clock frequency in Hz. */ uint32_t i3c_od_scl_hz; /* I3C open drain clock frequency in Hz. */ } clocks; #ifdef CONFIG_I3C_NPCX_DMA struct npcx_clk_cfg mdma_clk_subsys; struct mdma_reg *mdma_base; #endif }; struct npcx_i3c_data { /* Controller data */ struct i3c_driver_data common; /* Common i3c driver data */ struct k_mutex lock_mutex; /* Mutex of i3c controller */ struct k_sem sync_sem; /* Semaphore used for synchronization */ struct k_sem ibi_lock_sem; /* Semaphore used for ibi */ /* Target data */ struct i3c_target_config *target_config; /* Configuration parameters for I3C hardware to act as target device */ struct i3c_config_target config_target; struct k_sem target_lock_sem; /* Semaphore used for i3c target */ struct k_sem target_event_lock_sem; /* Semaphore used for i3c target ibi_raise() */ enum npcx_i3c_oper_state oper_state; /* Operation state */ #ifdef CONFIG_I3C_NPCX_DMA uint8_t mdma_rx_buf[4096]; #endif /* End of CONFIG_I3C_NPCX_DMA */ #ifdef CONFIG_I3C_USE_IBI struct { /* List of addresses used in the MIBIRULES register. */ uint8_t addr[5]; /* Number of valid addresses in MIBIRULES. */ uint8_t num_addr; /* True if all addresses have MSB set. */ bool msb; /* * True if all target devices require mandatory byte * for IBI. */ bool has_mandatory_byte; } ibi; #endif }; static void npcx_i3c_mutex_lock(const struct device *dev) { struct npcx_i3c_data *const data = dev->data; k_mutex_lock(&data->lock_mutex, K_FOREVER); } static void npcx_i3c_mutex_unlock(const struct device *dev) { struct npcx_i3c_data *const data = dev->data; k_mutex_unlock(&data->lock_mutex); } #ifdef CONFIG_I3C_NPCX_DMA static void i3c_ctrl_notify(const struct device *dev) { struct npcx_i3c_data *const data = dev->data; k_sem_give(&data->sync_sem); } static int i3c_ctrl_wait_completion(const struct device *dev) { struct npcx_i3c_data *const data = dev->data; return k_sem_take(&data->sync_sem, I3C_TRANS_TIMEOUT_MS); } static enum npcx_i3c_oper_state get_oper_state(const struct device *dev) { struct npcx_i3c_data *const data = dev->data; return data->oper_state; } #endif /* CONFIG_I3C_NPCX_DMA */ static void set_oper_state(const struct device *dev, enum npcx_i3c_oper_state state) { struct npcx_i3c_data *const data = dev->data; data->oper_state = state; } static uint8_t get_bus_available_match_val(uint32_t apb4_freq) { uint8_t bamatch; bamatch = DIV_ROUND_UP(apb4_freq, MHZ(1)); /* The clock of this counter is APB4_CLK divided by four */ bamatch = DIV_ROUND_UP(bamatch, BAMATCH_DIV); return bamatch; } /* * brief: Wait for status bit done and clear the status * * param[in] inst Pointer to I3C register. * * return 0, success * -ETIMEDOUT: check status timeout. */ static inline int npcx_i3c_status_wait_clear(struct i3c_reg *inst, uint8_t bit_offset) { if (WAIT_FOR(IS_BIT_SET(inst->MSTATUS, bit_offset), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { return -ETIMEDOUT; } inst->MSTATUS = BIT(bit_offset); /* W1C */ return 0; } static inline uint32_t npcx_i3c_state_get(struct i3c_reg *inst) { return GET_FIELD(inst->MSTATUS, NPCX_I3C_MSTATUS_STATE); } static inline void npcx_i3c_interrupt_all_disable(struct i3c_reg *inst) { uint32_t intmask = inst->MINTSET; inst->MINTCLR = intmask; } static inline void npcx_i3c_interrupt_enable(struct i3c_reg *inst, uint32_t mask) { inst->MINTSET = mask; } static void npcx_i3c_enable_target_interrupt(const struct device *dev, bool enable) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; /* Disable the target interrupt events */ inst->INTCLR = inst->INTSET; /* Clear the target interrupt status */ inst->STATUS = inst->STATUS; /* Enable the target interrupt events */ if (enable) { inst->INTSET = I3C_TGT_INTSET_MASK; inst->MINTSET |= BIT(NPCX_I3C_MINTSET_NOWCNTLR); /* I3C target is now controller */ #ifndef CONFIG_I3C_NPCX_DMA /* Receive buffer pending (FIFO mode) */ inst->INTSET |= BIT(NPCX_I3C_INTSET_RXPEND); #endif } } static bool npcx_i3c_has_error(struct i3c_reg *inst) { if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_ERRWARN)) { LOG_ERR("ERROR: MSTATUS 0x%08x MERRWARN 0x%08x", inst->MSTATUS, inst->MERRWARN); return true; } return false; } static inline void npcx_i3c_status_clear_all(struct i3c_reg *inst) { uint32_t mask = I3C_STATUS_CLR_MASK; inst->MSTATUS = mask; } static inline void npcx_i3c_errwarn_clear_all(struct i3c_reg *inst) { inst->MERRWARN = inst->MERRWARN; } static inline void npcx_i3c_fifo_flush(struct i3c_reg *inst) { inst->MDATACTRL |= (BIT(NPCX_I3C_MDATACTRL_FLUSHTB) | BIT(NPCX_I3C_MDATACTRL_FLUSHFB)); } /* * brief: Send request and check the request is valid * * param[in] inst Pointer to I3C register. * * return 0, success * -ETIMEDOUT check MCTRLDONE timeout. * -ENOSYS invalid use of request. */ static inline int npcx_i3c_send_request(struct i3c_reg *inst, uint32_t mctrl_val) { inst->MCTRL = mctrl_val; if (npcx_i3c_status_wait_clear(inst, NPCX_I3C_MSTATUS_MCTRLDONE) != 0) { return -ETIMEDOUT; } /* Check invalid use of request */ if (IS_BIT_SET(inst->MERRWARN, NPCX_I3C_MERRWARN_INVERQ)) { LOG_ERR("%s: Invalid request, merrwarn: %#x", __func__, inst->MERRWARN); return -ENOSYS; } return 0; } /* Start DAA procedure and continue the DAA with a Repeated START */ static inline int npcx_i3c_request_daa(struct i3c_reg *inst) { uint32_t val = 0; int ret; /* Set IBI response NACK while processing DAA */ SET_FIELD(val, NPCX_I3C_MCTRL_IBIRESP, MCTRL_IBIRESP_NACK); /* Send DAA request */ SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_PROCESSDAA); ret = npcx_i3c_send_request(inst, val); if (ret != 0) { LOG_ERR("Request DAA error, %d", ret); return ret; } return 0; } /* Tell controller to start auto IBI */ static inline int npcx_i3c_request_auto_ibi(struct i3c_reg *inst) { uint32_t val = 0; int ret; SET_FIELD(val, NPCX_I3C_MCTRL_IBIRESP, MCTRL_IBIRESP_ACK); SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_AUTOIBI); ret = npcx_i3c_send_request(inst, val); if (ret != 0) { LOG_ERR("Request auto ibi error, %d", ret); return ret; } return 0; } /* * brief: Controller emit start and send address * * param[in] inst Pointer to I3C register. * 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 in bytes. * If op_tye is HDR-DDR, the read_sz must be the number of words. * * return 0, success * else, error */ static int npcx_i3c_request_emit_start(struct i3c_reg *inst, uint8_t addr, enum npcx_i3c_mctrl_type op_type, bool is_read, size_t read_sz) { uint32_t mctrl = 0; int ret; /* Set request and target address*/ SET_FIELD(mctrl, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_EMITSTARTADDR); /* Set operation type */ SET_FIELD(mctrl, NPCX_I3C_MCTRL_TYPE, op_type); /* Set IBI response NACK in emit start */ SET_FIELD(mctrl, NPCX_I3C_MCTRL_IBIRESP, MCTRL_IBIRESP_NACK); /* Set dynamic address */ SET_FIELD(mctrl, NPCX_I3C_MCTRL_ADDR, addr); /* Set read(1) or write(0) */ if (is_read) { mctrl |= BIT(NPCX_I3C_MCTRL_DIR); SET_FIELD(mctrl, NPCX_I3C_MCTRL_RDTERM, read_sz); /* Set read length */ } else { mctrl &= ~BIT(NPCX_I3C_MCTRL_DIR); } ret = npcx_i3c_send_request(inst, mctrl); if (ret != 0) { LOG_ERR("Request start error, %d", ret); return ret; } /* Check NACK after MCTRLDONE is get */ if (IS_BIT_SET(inst->MERRWARN, NPCX_I3C_MERRWARN_NACK)) { LOG_DBG("Address nacked"); return -ENODEV; } return 0; } /* * brief: Controller emit STOP. * * This emits STOP when controller is in NORMACT state. * * param[in] inst Pointer to I3C register. * * return 0 success * -ECANCELED i3c state not as expected. * -ETIMEDOUT check MCTRLDONE timeout. * -ENOSYS invalid use of request. */ static inline int npcx_i3c_request_emit_stop(struct i3c_reg *inst) { uint32_t val = 0; int ret; uint32_t i3c_state = npcx_i3c_state_get(inst); /* Make sure we are in a state where we can emit STOP */ if (i3c_state == MSTATUS_STATE_IDLE) { LOG_WRN("Request stop in idle state, state= %#x", i3c_state); return -ECANCELED; } SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_EMITSTOP); ret = npcx_i3c_send_request(inst, val); if (ret != 0) { LOG_ERR("Request stop error, %d", ret); return ret; } 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("Current working state=%d", 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; int ret; SET_FIELD(val, NPCX_I3C_MCTRL_IBIRESP, MCTRL_IBIRESP_NACK); SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_IBIACKNACK); ret = npcx_i3c_send_request(inst, val); if (ret != 0) { LOG_ERR("Request ibi_rsp nack error, %d", ret); return ret; } return 0; } static inline int npcx_i3c_ibi_respond_ack(struct i3c_reg *inst) { uint32_t val = 0; int ret; SET_FIELD(val, NPCX_I3C_MCTRL_IBIRESP, MCTRL_IBIRESP_ACK); SET_FIELD(val, NPCX_I3C_MCTRL_REQUEST, MCTRL_REQUEST_IBIACKNACK); ret = npcx_i3c_send_request(inst, val); if (ret != 0) { LOG_ERR("Request ibi_rsp ack error %d", ret); return ret; } return 0; } /* * brief: Find a registered I3C target device. * * This returns the I3C device descriptor of the I3C device * matching the incoming id. * * param[in] dev Pointer to controller device driver instance. * param[in] id Pointer to I3C device ID. * * return see i3c_device_find. */ static inline struct i3c_device_desc *npcx_i3c_device_find(const struct device *dev, const struct i3c_device_id *id) { const struct npcx_i3c_config *config = dev->config; return i3c_dev_list_find(&config->common.dev_list, id); } /* * brief: Perform bus recovery. * * param[in] dev Pointer to controller device driver instance. * * return 0 success, otherwise error */ static int npcx_i3c_recover_bus(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; /* * If the controller is in NORMACT state, tells it to emit STOP * so it can return to IDLE, or is ready to clear any pending * target initiated IBIs. */ if (npcx_i3c_state_get(inst) == MSTATUS_STATE_NORMACT) { npcx_i3c_request_emit_stop(inst); }; /* Exhaust all target initiated IBI */ while (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_TGTSTART)) { inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_TGTSTART); /* W1C */ /* Tell the controller to perform auto IBI. */ npcx_i3c_request_auto_ibi(inst); if (WAIT_FOR(IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { break; } /* Once auto IBI is done, discard bytes in FIFO. */ while (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_RXPEND)) { /* Flush FIFO as long as RXPEND is set. */ npcx_i3c_fifo_flush(inst); } /* * There might be other IBIs waiting. * So pause a bit to let other targets initiates * their IBIs. */ k_busy_wait(100); } /* Check IDLE state */ if (WAIT_FOR((npcx_i3c_state_get(inst) == MSTATUS_STATE_IDLE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { return -EBUSY; } return 0; } static inline void npcx_i3c_xfer_reset(struct i3c_reg *inst) { npcx_i3c_status_clear_all(inst); npcx_i3c_errwarn_clear_all(inst); npcx_i3c_fifo_flush(inst); } /* * brief: Perform one write transaction. * * This writes all data in buf to TX FIFO or time out * waiting for FIFO spaces. * * param[in] inst Pointer to controller registers. * param[in] buf Buffer containing data to be sent. * param[in] buf_sz Number of bytes in buf to send. * param[in] no_ending True, not including ending byte in message. * False, including ending byte in message * * return Number of bytes written, or negative if error. * */ static int npcx_i3c_xfer_write_fifo(struct i3c_reg *inst, uint8_t *buf, uint8_t buf_sz, bool no_ending) { int offset = 0; int remaining = buf_sz; while (remaining > 0) { /* Check tx fifo not full */ if (WAIT_FOR(!IS_BIT_SET(inst->MDATACTRL, NPCX_I3C_MDATACTRL_TXFULL), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { LOG_DBG("Check tx fifo not full timed out"); return -ETIMEDOUT; } if ((remaining > 1) || no_ending) { inst->MWDATAB = (uint32_t)buf[offset]; } else { inst->MWDATABE = (uint32_t)buf[offset]; /* Set last byte */ } offset += 1; remaining -= 1; } return offset; } /* * brief: Perform read transaction. * * This reads from RX FIFO until COMPLETE bit is set in MSTATUS * or time out. * * param[in] inst Pointer to controller registers. * param[in] buf Buffer to store data. * param[in] buf_sz Number of bytes to read. * * return Number of bytes read, or negative if error. * */ static int npcx_i3c_xfer_read_fifo(struct i3c_reg *inst, uint8_t *buf, uint8_t rd_sz) { bool is_done = false; int offset = 0; while (is_done == false) { /* Check message is terminated */ if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE)) { is_done = true; } /* Check I3C bus error */ if (npcx_i3c_has_error(inst)) { /* Check timeout*/ if (IS_BIT_SET(inst->MERRWARN, NPCX_I3C_MERRWARN_TIMEOUT)) { LOG_WRN("%s: ERR: timeout", __func__); } inst->MERRWARN = inst->MERRWARN; return -EIO; } /* Check rx not empty */ if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_RXPEND)) { /* Receive all the data in this round. * Read in a tight loop to reduce chance of losing * FIFO data when the i3c speed is high. */ while (offset < rd_sz) { if (GET_FIELD(inst->MDATACTRL, NPCX_I3C_MDATACTRL_RXCOUNT) == 0) { break; } buf[offset++] = (uint8_t)inst->MRDATAB; } } } return offset; } #ifdef CONFIG_I3C_NPCX_DMA /* * brief: Perform DMA write transaction. * * For write end, use the interrupt generated by COMPLETE bit in MSTATUS register. * * param[in] dev Pointer to controller device driver instance. * param[in] buf Buffer to store data. * param[in] buf_sz Number of bytes to read. * * return Number of bytes read, or negative if error. * */ static int npcx_i3c_xfer_write_fifo_dma(const struct device *dev, uint8_t *buf, uint32_t buf_sz) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; int ret; set_oper_state(dev, NPCX_I3C_OP_STATE_WR); /* Enable I3C MDMA write for one frame */ SET_FIELD(i3c_inst->MDMACTRL, NPCX_I3C_MDMACTRL_DMATB, MDMA_DMATB_EN_ONE_FRAME); i3c_inst->MINTSET |= BIT(NPCX_I3C_MINTCLR_COMPLETE); /* Enable I3C complete interrupt */ /* Write Operation (MDMA CH_1) */ mdma_inst->MDMA_TCNT1 = buf_sz; /* Set MDMA transfer count */ mdma_inst->MDMA_SRCB1 = (uint32_t)buf; /* Set source address */ mdma_inst->MDMA_CTL1 |= BIT(NPCX_MDMA_CTL_MDMAEN); /* Start DMA transfer */ /* Wait I3C COMPLETE */ ret = i3c_ctrl_wait_completion(dev); if (ret < 0) { LOG_DBG("Check complete time out, buf_size:%d", buf_sz); goto out_wr_fifo_dma; } /* Check and clear DMA TC after complete */ if (!IS_BIT_SET(mdma_inst->MDMA_CTL1, NPCX_MDMA_CTL_TC)) { LOG_DBG("DMA busy, TC=%d", IS_BIT_SET(mdma_inst->MDMA_CTL1, NPCX_MDMA_CTL_TC)); ret = -EBUSY; goto out_wr_fifo_dma; } mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_TC); /* Clear TC, W0C */ ret = buf_sz - mdma_inst->MDMA_CTCNT1; /* Set transferred count */ LOG_DBG("Write cnt=%d", ret); out_wr_fifo_dma: i3c_inst->MINTCLR |= BIT(NPCX_I3C_MINTCLR_COMPLETE); /* Disable I3C complete interrupt */ npcx_i3c_fifo_flush(i3c_inst); set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); return ret; } /* * 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. * * param[in] dev Pointer to controller device driver instance. * param[in] buf Buffer to store data. * param[in] buf_sz Number of bytes to read. * * return Number of bytes read, or negative if error. * */ static int npcx_i3c_xfer_read_fifo_dma(const struct device *dev, uint8_t *buf, uint32_t buf_sz) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; int ret; set_oper_state(dev, NPCX_I3C_OP_STATE_RD); /* Enable DMA until DMA is disabled by setting DMAFB to 00 */ SET_FIELD(i3c_inst->MDMACTRL, NPCX_I3C_MDMACTRL_DMAFB, MDMA_DMAFB_EN_MANUAL); /* Read Operation (MDMA CH_0) */ mdma_inst->MDMA_TCNT0 = buf_sz; /* Set MDMA transfer count */ mdma_inst->MDMA_DSTB0 = (uint32_t)buf; /* Set destination address */ mdma_inst->MDMA_CTL0 |= BIT(NPCX_MDMA_CTL_SIEN); /* Enable stop interrupt */ mdma_inst->MDMA_CTL0 |= BIT(NPCX_MDMA_CTL_MDMAEN); /* Start DMA transfer */ /* Wait MDMA TC */ ret = i3c_ctrl_wait_completion(dev); if (ret < 0) { LOG_DBG("Check DMA done time out"); } else { ret = buf_sz - mdma_inst->MDMA_CTCNT0; /* Set transferred count */ LOG_DBG("Read cnt=%d", ret); } mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_SIEN); /* Disable stop interrupt */ /* Disable I3C MDMA read */ SET_FIELD(i3c_inst->MDMACTRL, NPCX_I3C_MDMACTRL_DMAFB, MDMA_DMAFB_DISABLE); npcx_i3c_fifo_flush(i3c_inst); set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); return ret; } /* * brief: Perform one transfer transaction by DMA. * (Support SDR and HDR-DDR) * * param[in] inst Pointer to controller registers. * param[in] addr Target address. * param[in] op_type Request type. * param[in] buf Buffer for data to be sent or received. * param[in] buf_sz Buffer size in bytes. * param[in] is_read True if this is a read transaction, false if write. * param[in] emit_start True if START is needed before read/write. * param[in] emit_stop True if STOP is needed after read/write. * * return Number of bytes read/written, or negative if error. */ 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, 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) { /* * 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; } } /* No data to be transferred */ if ((buf == NULL) || (buf_sz == 0)) { goto out_do_one_xfer_dma; } /* Select read or write operation */ if (is_read) { ret = npcx_i3c_xfer_read_fifo_dma(dev, buf, buf_sz); } else { ret = npcx_i3c_xfer_write_fifo_dma(dev, buf, buf_sz); } if (ret < 0) { LOG_ERR("%s: %s fifo fail", __func__, is_read ? "read" : "write"); goto out_do_one_xfer_dma; } /* Check I3C bus error */ if (npcx_i3c_has_error(inst)) { ret = -EIO; LOG_ERR("%s: I3C bus error", __func__); } out_do_one_xfer_dma: /* Emit STOP or exit DDR if needed */ if (emit_stop) { npcx_i3c_xfer_stop(inst); } return ret; } #endif /* End of CONFIG_I3C_NPCX_DMA */ /* * brief: Perform one transfer transaction. * (Support SDR only) * * param[in] inst Pointer to controller registers. * param[in] addr Target address. * param[in] op_type Request type. * param[in] buf Buffer for data to be sent or received. * param[in] buf_sz Buffer size in bytes. * param[in] is_read True if this is a read transaction, false if write. * param[in] emit_start True if START is needed before read/write. * param[in] emit_stop True if STOP is needed after read/write. * param[in] no_ending True if not to signal end of write message. * * return Number of bytes read/written, or negative if error. */ static int npcx_i3c_do_one_xfer(struct i3c_reg *inst, 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 no_ending) { int ret = 0; npcx_i3c_status_clear_all(inst); npcx_i3c_errwarn_clear_all(inst); /* Emit START if needed */ if (emit_start) { ret = npcx_i3c_request_emit_start(inst, addr, op_type, is_read, buf_sz); if (ret != 0) { LOG_ERR("%s: emit start fail", __func__); goto out_do_one_xfer; } } /* No data to be transferred */ if ((buf == NULL) || (buf_sz == 0)) { goto out_do_one_xfer; } /* Select read or write operation */ if (is_read) { ret = npcx_i3c_xfer_read_fifo(inst, buf, buf_sz); } else { ret = npcx_i3c_xfer_write_fifo(inst, buf, buf_sz, no_ending); } if (ret < 0) { LOG_ERR("%s: %s fifo fail", __func__, is_read ? "read" : "write"); goto out_do_one_xfer; } /* Check message complete if is a read transaction or * ending byte of a write transaction. */ if (is_read || !no_ending) { /* Wait message transfer complete */ if (WAIT_FOR(IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { LOG_DBG("Wait COMPLETE timed out, addr 0x%02x, buf_sz %u", addr, buf_sz); ret = -ETIMEDOUT; emit_stop = true; goto out_do_one_xfer; } inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_COMPLETE); /* W1C */ } /* Check I3C bus error */ if (npcx_i3c_has_error(inst)) { ret = -EIO; LOG_ERR("%s: I3C bus error", __func__); } out_do_one_xfer: /* Emit STOP if needed */ if (emit_stop) { npcx_i3c_request_emit_stop(inst); } return ret; } /* * brief: Transfer messages in I3C mode. * * see i3c_transfer * * param[in] dev Pointer to device driver instance. * param[in] target Pointer to target device descriptor. * param[in] msgs Pointer to I3C messages. * param[in] num_msgs Number of messages to transfers. * * return see i3c_transfer */ static int npcx_i3c_transfer(const struct device *dev, struct i3c_device_desc *target, struct i3c_msg *msgs, uint8_t num_msgs) { 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 = 0; int ret = 0; bool send_broadcast = true; bool is_xfer_done = true; enum npcx_i3c_mctrl_type op_type; if (msgs == NULL) { return -EINVAL; } if (target->dynamic_addr == 0U) { return -EINVAL; } npcx_i3c_mutex_lock(dev); /* Check bus in idle state */ if (WAIT_FOR((npcx_i3c_state_get(inst) == MSTATUS_STATE_IDLE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { LOG_ERR("%s: xfer state error: %d", __func__, npcx_i3c_state_get(inst)); npcx_i3c_mutex_unlock(dev); return -ETIMEDOUT; } /* Disable interrupt */ intmask = inst->MINTSET; npcx_i3c_interrupt_all_disable(inst); npcx_i3c_xfer_reset(inst); /* 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. */ bool is_read = (msgs[i].flags & I3C_MSG_RW_MASK) == I3C_MSG_READ; bool no_ending = false; /* * Emit start if this is the first message or that * the RESTART flag is set in message. */ #ifdef CONFIG_I3C_NPCX_DMA bool emit_start = (i == 0) || ((msgs[i].flags & I3C_MSG_RESTART) == I3C_MSG_RESTART); #endif bool emit_stop = (msgs[i].flags & I3C_MSG_STOP) == I3C_MSG_STOP; /* * The controller requires special treatment of last byte of * a write message. Since the API permits having a bunch of * write messages without RESTART in between, this is just some * logic to determine whether to treat the last byte of this * message to be the last byte of a series of write mssages. * If not, tell the write function not to treat it that way. */ if (!is_read && !emit_stop && ((i + 1) != num_msgs)) { bool next_is_write = (msgs[i + 1].flags & I3C_MSG_RW_MASK) == I3C_MSG_WRITE; bool next_is_restart = ((msgs[i + 1].flags & I3C_MSG_RESTART) == I3C_MSG_RESTART); /* Check next msg is still write operation and not including Sr */ if (next_is_write && !next_is_restart) { no_ending = true; } } #ifdef CONFIG_I3C_NPCX_DMA /* Current DMA not support multi-message write */ if (!is_read && no_ending) { LOG_ERR("I3C DMA transfer not support multi-message write"); ret = -EINVAL; break; } #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 */ /* * 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; } } 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, 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 */ break; } /* Write back the total number of bytes transferred */ msgs[i].num_xfer = xfered_len; if (emit_stop) { /* SDR. After a STOP, send broadcast header before next msg */ send_broadcast = true; } /* Check emit stop flag including in the final msg */ if ((i == num_msgs - 1) && (emit_stop == false)) { is_xfer_done = false; } } /* Emit stop if error occurs or stop flag not in the msg */ if ((ret != 0) || (is_xfer_done == false)) { npcx_i3c_xfer_stop(inst); } npcx_i3c_errwarn_clear_all(inst); npcx_i3c_status_clear_all(inst); npcx_i3c_interrupt_enable(inst, intmask); npcx_i3c_mutex_unlock(dev); return ret; } /* * brief: Perform Dynamic Address Assignment. * * param[in] dev Pointer to controller device driver instance. * * return 0 If successful. * -EBUSY Bus is busy. * -EIO General input / output error. * -ENODEV If a provisioned ID does not match to any target devices * in the registered device list. * -ENOSPC No more free addresses can be assigned to target. * -ENOSYS Dynamic address assignment is not supported by * the controller driver. */ static int npcx_i3c_do_daa(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; int ret = 0; uint8_t rx_buf[8]; size_t rx_count; uint32_t intmask; npcx_i3c_mutex_lock(dev); memset(rx_buf, 0xff, sizeof(rx_buf)); /* Check bus in idle state */ if (WAIT_FOR((npcx_i3c_state_get(inst) == MSTATUS_STATE_IDLE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { LOG_ERR("%s: DAA state error: %d", __func__, npcx_i3c_state_get(inst)); npcx_i3c_mutex_unlock(dev); return -ETIMEDOUT; } LOG_DBG("DAA: ENTDAA"); /* Disable interrupt */ intmask = inst->MINTSET; npcx_i3c_interrupt_all_disable(inst); npcx_i3c_xfer_reset(inst); /* Emit process DAA */ if (npcx_i3c_request_daa(inst) != 0) { ret = -ETIMEDOUT; LOG_ERR("Emit process DAA error"); goto out_do_daa; } /* Loop until no more responses from devices */ do { /* Check ERRWARN bit set */ if (npcx_i3c_has_error(inst)) { ret = -EIO; LOG_ERR("DAA recv error"); break; } /* Receive Provisioned ID, BCR and DCR (total 8 bytes) */ rx_count = GET_FIELD(inst->MDATACTRL, NPCX_I3C_MDATACTRL_RXCOUNT); if (rx_count == DAA_TGT_INFO_SZ) { for (int i = 0; i < rx_count; i++) { rx_buf[i] = (uint8_t)inst->MRDATAB; } } else { /* Data count not as expected, exit DAA */ ret = -EBADMSG; LOG_DBG("Rx count not as expected %d, abort DAA", rx_count); break; } /* Start assign dynamic address */ if ((npcx_i3c_state_get(inst) == MSTATUS_STATE_DAA) && IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_BETWEEN)) { struct i3c_device_desc *target; uint16_t vendor_id; uint32_t part_no; uint64_t pid; uint8_t dyn_addr = 0; /* PID[47:33] = manufacturer ID */ vendor_id = (((uint16_t)rx_buf[0] << 8U) | (uint16_t)rx_buf[1]) & 0xFFFEU; /* PID[31:0] = vendor fixed falue or random value */ part_no = (uint32_t)rx_buf[2] << 24U | (uint32_t)rx_buf[3] << 16U | (uint32_t)rx_buf[4] << 8U | (uint32_t)rx_buf[5]; /* Combine into one Provisioned ID */ pid = (uint64_t)vendor_id << 32U | (uint64_t)part_no; LOG_DBG("DAA: Rcvd PID 0x%04x%08x", vendor_id, part_no); /* Find a usable address during ENTDAA */ ret = i3c_dev_list_daa_addr_helper(&data->common.attached_dev.addr_slots, &config->common.dev_list, pid, false, false, &target, &dyn_addr); if (ret != 0) { LOG_ERR("%s: Assign new DA error", __func__); break; } if (target == NULL) { LOG_INF("%s: PID 0x%04x%08x is not in registered device " "list, given dynamic address 0x%02x", dev->name, vendor_id, part_no, dyn_addr); } else { /* Update target descriptor */ target->dynamic_addr = dyn_addr; target->bcr = rx_buf[6]; target->dcr = rx_buf[7]; } /* Mark the address as I3C device */ i3c_addr_slots_mark_i3c(&data->common.attached_dev.addr_slots, dyn_addr); /* * If the device has static address, after address assignment, * the device will not respond to the static address anymore. * So free the static one from address slots if different from * newly assigned one. */ if ((target != NULL) && (target->static_addr != 0U) && (dyn_addr != target->static_addr)) { i3c_addr_slots_mark_free(&data->common.attached_dev.addr_slots, dyn_addr); } /* Emit process DAA again to send the address to the device */ inst->MWDATAB = dyn_addr; ret = npcx_i3c_request_daa(inst); if (ret != 0) { LOG_ERR("%s: Assign DA timeout", __func__); break; } LOG_DBG("PID 0x%04x%08x assigned dynamic address 0x%02x", vendor_id, part_no, dyn_addr); /* Target did not accept the assigned DA, exit DAA */ if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_NACKED)) { ret = -EFAULT; LOG_DBG("TGT NACK assigned DA %#x", dyn_addr); /* Free the reserved DA */ i3c_addr_slots_mark_free(&data->common.attached_dev.addr_slots, dyn_addr); /* 0 if address has not been assigned */ if (target != NULL) { target->dynamic_addr = 0; } break; } } /* Check all targets have been assigned DA and DAA complete */ } while ((!IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE)) && npcx_i3c_state_get(inst) != MSTATUS_STATE_IDLE); out_do_daa: /* Exit DAA mode when error occurs */ if (ret != 0) { npcx_i3c_request_emit_stop(inst); } /* Clear all flags. */ npcx_i3c_errwarn_clear_all(inst); npcx_i3c_status_clear_all(inst); /* Re-Enable I3C IRQ sources. */ npcx_i3c_interrupt_enable(inst, intmask); npcx_i3c_fifo_flush(inst); npcx_i3c_mutex_unlock(dev); return ret; } /* * brief: Send Common Command Code (CCC). * * param[in] dev Pointer to controller device driver instance. * param[in] payload Pointer to CCC payload. * * return: The same as i3c_do_ccc() * 0 If successful. * -EBUSY Bus is busy. * -EIO General Input / output error. * -EINVAL Invalid valid set in the payload structure. * -ENOSYS Not implemented. */ static int npcx_i3c_do_ccc(const struct device *dev, struct i3c_ccc_payload *payload) { const struct npcx_i3c_config *config = dev->config; int ret; struct i3c_reg *inst = config->base; uint32_t intmask; int xfered_len; if (dev == NULL || payload == NULL) { return -EINVAL; } npcx_i3c_mutex_lock(dev); /* Disable interrupt */ intmask = inst->MINTSET; npcx_i3c_interrupt_all_disable(inst); /* Clear status and flush fifo */ npcx_i3c_xfer_reset(inst); LOG_DBG("CCC[0x%02x]", payload->ccc.id); /* Write emit START and broadcast address (0x7E) */ ret = npcx_i3c_request_emit_start(inst, I3C_BROADCAST_ADDR, NPCX_I3C_MCTRL_TYPE_I3C, false, 0); if (ret < 0) { LOG_ERR("CCC[0x%02x] %s START error (%d)", payload->ccc.id, i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", ret); goto out_do_ccc; } /* Write CCC command */ npcx_i3c_status_clear_all(inst); npcx_i3c_errwarn_clear_all(inst); xfered_len = npcx_i3c_xfer_write_fifo(inst, &payload->ccc.id, 1, payload->ccc.data_len > 0); if (xfered_len < 0) { LOG_ERR("CCC[0x%02x] %s command error (%d)", payload->ccc.id, i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", ret); ret = xfered_len; goto out_do_ccc; } /* Write data (defining byte or data bytes) for CCC if needed */ if (payload->ccc.data_len > 0) { npcx_i3c_status_clear_all(inst); npcx_i3c_errwarn_clear_all(inst); xfered_len = npcx_i3c_xfer_write_fifo(inst, payload->ccc.data, payload->ccc.data_len, false); if (xfered_len < 0) { LOG_ERR("CCC[0x%02x] %s command payload error (%d)", payload->ccc.id, i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", ret); ret = xfered_len; goto out_do_ccc; } /* Write back the transferred bytes */ payload->ccc.num_xfer = xfered_len; } /* Wait message transfer complete */ if (WAIT_FOR(IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { ret = -ETIMEDOUT; LOG_DBG("Check complete timeout"); goto out_do_ccc; } inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_COMPLETE); /* W1C */ /* For direct CCC */ if (!i3c_ccc_is_payload_broadcast(payload)) { /* * If there are payload(s) for each target, * RESTART and then send payload for each target. */ for (int idx = 0; idx < payload->targets.num_targets; idx++) { struct i3c_ccc_target_payload *tgt_payload = &payload->targets.payloads[idx]; bool is_read = (tgt_payload->rnw == 1U); xfered_len = npcx_i3c_do_one_xfer( inst, tgt_payload->addr, NPCX_I3C_MCTRL_TYPE_I3C, tgt_payload->data, tgt_payload->data_len, is_read, true, false, false); if (xfered_len < 0) { LOG_ERR("CCC[0x%02x] target payload error (%d)", payload->ccc.id, ret); ret = xfered_len; goto out_do_ccc; } /* Write back the total number of bytes transferred */ tgt_payload->num_xfer = xfered_len; } } out_do_ccc: npcx_i3c_request_emit_stop(inst); npcx_i3c_interrupt_enable(inst, intmask); npcx_i3c_mutex_unlock(dev); return ret; } #ifdef CONFIG_I3C_USE_IBI /* * brief Callback to service target initiated IBIs in workqueue. * * param[in] work Pointer to k_work item. */ static void npcx_i3c_ibi_work(struct k_work *work) { uint8_t payload[CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE]; size_t payload_sz = 0; struct i3c_ibi_work *i3c_ibi_work = CONTAINER_OF(work, struct i3c_ibi_work, work); const struct device *dev = i3c_ibi_work->controller; const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; struct i3c_device_desc *target = NULL; uint32_t ibitype, ibiaddr; int ret; k_sem_take(&data->ibi_lock_sem, K_FOREVER); if (npcx_i3c_state_get(inst) != MSTATUS_STATE_TGTREQ) { LOG_DBG("IBI work %p running not because of IBI", work); LOG_ERR("%s: IBI not in TGTREQ state, state : %#x", __func__, npcx_i3c_state_get(inst)); LOG_ERR("%s: MSTATUS 0x%08x MERRWARN 0x%08x", __func__, inst->MSTATUS, inst->MERRWARN); npcx_i3c_request_emit_stop(inst); goto out_ibi_work; }; /* Use auto IBI to service the IBI */ npcx_i3c_request_auto_ibi(inst); /* Wait for target to win address arbitration (ibitype and ibiaddr) */ if (WAIT_FOR(IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_IBIWON), NPCX_I3C_CHK_TIMEOUT_US, NULL) == false) { LOG_ERR("IBI work, IBIWON timeout"); LOG_ERR("%s: MSTATUS 0x%08x MERRWARN 0x%08x", __func__, inst->MSTATUS, inst->MERRWARN); npcx_i3c_request_emit_stop(inst); goto out_ibi_work; } ibitype = GET_FIELD(inst->MSTATUS, NPCX_I3C_MSTATUS_IBITYPE); ibiaddr = GET_FIELD(inst->MSTATUS, NPCX_I3C_MSTATUS_IBIADDR); switch (ibitype) { case MSTATUS_IBITYPE_IBI: ret = npcx_i3c_xfer_read_fifo(inst, &payload[0], sizeof(payload)); if (ret >= 0) { payload_sz = (size_t)ret; } else { LOG_ERR("Error reading IBI payload"); npcx_i3c_request_emit_stop(inst); goto out_ibi_work; } break; case MSTATUS_IBITYPE_HJ: npcx_i3c_ibi_respond_ack(inst); npcx_i3c_request_emit_stop(inst); break; case MSTATUS_IBITYPE_CR: LOG_DBG("Controller role handoff not supported"); npcx_i3c_ibi_respond_nack(inst); npcx_i3c_request_emit_stop(inst); break; default: break; } if (npcx_i3c_has_error(inst)) { LOG_ERR("%s: unexpected error, ibi type:%d", __func__, ibitype); /* * If the controller detects any errors, simply * emit a STOP to abort the IBI. The target will * raise IBI again if so desired. */ npcx_i3c_request_emit_stop(inst); goto out_ibi_work; } switch (ibitype) { case MSTATUS_IBITYPE_IBI: target = i3c_dev_list_i3c_addr_find(dev, (uint8_t)ibiaddr); if (target != NULL) { if (i3c_ibi_work_enqueue_target_irq(target, &payload[0], payload_sz) != 0) { LOG_ERR("Error enqueue IBI IRQ work"); } } else { LOG_ERR("IBI (MDB) target not in the list"); } /* Finishing the IBI transaction */ npcx_i3c_request_emit_stop(inst); break; case MSTATUS_IBITYPE_HJ: if (i3c_ibi_work_enqueue_hotjoin(dev) != 0) { LOG_ERR("Error enqueue IBI HJ work"); } break; case MSTATUS_IBITYPE_CR: /* Not supported, for future use. */ break; default: break; } out_ibi_work: npcx_i3c_xfer_reset(inst); k_sem_give(&data->ibi_lock_sem); /* Re-enable target initiated IBI interrupt. */ inst->MINTSET = BIT(NPCX_I3C_MINTSET_TGTSTART); } /* Set local IBI information to IBIRULES register */ static void npcx_i3c_ibi_rules_setup(struct npcx_i3c_data *data, struct i3c_reg *inst) { uint32_t ibi_rules; int idx; ibi_rules = 0; for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { uint32_t addr_6bit; /* Extract the lower 6-bit of target address */ addr_6bit = (uint32_t)data->ibi.addr[idx] & IBIRULES_ADDR_MSK; /* Shift into correct place */ addr_6bit <<= idx * IBIRULES_ADDR_SHIFT; /* Put into the temporary IBI Rules register */ ibi_rules |= addr_6bit; } if (!data->ibi.msb) { /* The MSB0 field is 1 if MSB is 0 */ ibi_rules |= BIT(NPCX_I3C_IBIRULES_MSB0); } if (!data->ibi.has_mandatory_byte) { /* The NOBYTE field is 1 if there is no mandatory byte */ ibi_rules |= BIT(NPCX_I3C_IBIRULES_NOBYTE); } /* Update the register */ inst->IBIRULES = ibi_rules; LOG_DBG("MIBIRULES 0x%08x", ibi_rules); } static int npcx_i3c_ibi_enable(const struct device *dev, struct i3c_device_desc *target) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; struct i3c_ccc_events i3c_events; uint8_t idx; bool msb, has_mandatory_byte; int ret; /* Check target IBI request capable */ if (!i3c_device_is_ibi_capable(target)) { LOG_ERR("%s: device is not ibi capable", __func__); return -EINVAL; } if (data->ibi.num_addr >= ARRAY_SIZE(data->ibi.addr)) { /* No more free entries in the IBI Rules table */ LOG_ERR("%s: no more free space in the IBI rules table", __func__); return -ENOMEM; } /* Check whether the selected target is already in the list */ for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { if (data->ibi.addr[idx] == target->dynamic_addr) { LOG_ERR("%s: selected target is already in the list", __func__); return -EINVAL; } } /* Disable controller interrupt while we configure IBI rules. */ inst->MINTCLR = BIT(NPCX_I3C_MINTCLR_TGTSTART); LOG_DBG("IBI enabling for 0x%02x (BCR 0x%02x)", target->dynamic_addr, target->bcr); msb = (target->dynamic_addr & BIT(6)) == BIT(6); /* Check addess(7-bit) MSB enable */ has_mandatory_byte = i3c_ibi_has_payload(target); /* * If there are already addresses in the table, we must * check if the incoming entry is compatible with * the existing ones. * * All targets in the list should follow the same IBI rules. */ if (data->ibi.num_addr > 0) { /* * 1. All devices in the table must all use mandatory * bytes, or do not. * * 2. Each address in entry only captures the lowest 6-bit. * The MSB (7th bit) is captured separated in another bit * in the register. So all addresses must have the same MSB. */ if ((has_mandatory_byte != data->ibi.has_mandatory_byte) || (msb != data->ibi.msb)) { ret = -EINVAL; LOG_ERR("%s: New IBI does not have same mandatory byte or msb" " as previous IBI", __func__); goto out_ibi_enable; } /* Find an empty address slot */ for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { if (data->ibi.addr[idx] == 0U) { break; } } if (idx >= ARRAY_SIZE(data->ibi.addr)) { ret = -ENOTSUP; LOG_ERR("Cannot support more IBIs"); goto out_ibi_enable; } } else { /* * If the incoming address is the first in the table, * it dictates future compatibilities. */ data->ibi.has_mandatory_byte = has_mandatory_byte; data->ibi.msb = msb; idx = 0; } data->ibi.addr[idx] = target->dynamic_addr; data->ibi.num_addr += 1U; npcx_i3c_ibi_rules_setup(data, inst); /* Enable target IBI event by ENEC command */ i3c_events.events = I3C_CCC_EVT_INTR; ret = i3c_ccc_do_events_set(target, true, &i3c_events); if (ret != 0) { LOG_ERR("Error sending IBI ENEC for 0x%02x (%d)", target->dynamic_addr, ret); } out_ibi_enable: if (data->ibi.num_addr > 0U) { /* * If there is more than 1 target in the list, * enable controller to raise interrupt when a target * initiates IBI. */ inst->MINTSET = BIT(NPCX_I3C_MINTSET_TGTSTART); } return ret; } static int npcx_i3c_ibi_disable(const struct device *dev, struct i3c_device_desc *target) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; struct i3c_ccc_events i3c_events; int ret; int idx; if (!i3c_device_is_ibi_capable(target)) { LOG_ERR("%s: device is not ibi capable", __func__); return -EINVAL; } for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { if (target->dynamic_addr == data->ibi.addr[idx]) { break; } } if (idx == ARRAY_SIZE(data->ibi.addr)) { LOG_ERR("%s: target is not in list of registered addresses", __func__); return -ENODEV; } /* Disable controller interrupt while we configure IBI rules. */ inst->MINTCLR = BIT(NPCX_I3C_MINTCLR_TGTSTART); /* Clear the ibi rule data */ data->ibi.addr[idx] = 0U; data->ibi.num_addr -= 1U; /* Disable disable target IBI */ i3c_events.events = I3C_CCC_EVT_INTR; ret = i3c_ccc_do_events_set(target, false, &i3c_events); if (ret != 0) { LOG_ERR("Error sending IBI DISEC for 0x%02x (%d)", target->dynamic_addr, ret); } npcx_i3c_ibi_rules_setup(data, inst); if (data->ibi.num_addr > 0U) { /* * Enable controller to raise interrupt when a target * initiates IBI. */ inst->MINTSET = BIT(NPCX_I3C_MINTSET_TGTSTART); } return ret; } #endif /* CONFIG_I3C_USE_IBI */ static int npcx_i3c_target_ibi_raise(const struct device *dev, struct i3c_ibi *request) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; struct npcx_i3c_data *data = dev->data; int index; /* the request or the payload were not specific */ if ((request == NULL) || ((request->payload_len) && (request->payload == NULL))) { return -EINVAL; } /* the I3C was not in target mode or the bus is in HDR mode now */ if (!IS_BIT_SET(inst->CONFIG, NPCX_I3C_CONFIG_TGTENA) || IS_BIT_SET(inst->STATUS, NPCX_I3C_STATUS_STHDR)) { return -EINVAL; } switch (request->ibi_type) { case I3C_IBI_TARGET_INTR: if (IS_BIT_SET(inst->STATUS, NPCX_I3C_STATUS_IBIDIS)) { return -ENOTSUP; } if (request->payload_len == 0) { LOG_ERR("%s: IBI invalid payload_len, len: %#x", __func__, request->payload_len); return -EINVAL; } k_sem_take(&data->target_event_lock_sem, K_FOREVER); set_oper_state(dev, NPCX_I3C_OP_STATE_IBI); /* Mandatory data byte */ SET_FIELD(inst->CTRL, NPCX_I3C_CTRL_IBIDATA, request->payload[0]); /* Extended data */ if (request->payload_len > 1) { if (request->payload_len <= 32) { for (index = 1; index < (request->payload_len - 1); index++) { inst->WDATAB = request->payload[index]; } inst->WDATABE = request->payload[index]; } else { /* transfer data from MDMA */ } SET_FIELD(inst->IBIEXT1, NPCX_I3C_IBIEXT1_CNT, 0); inst->CTRL |= BIT(NPCX_I3C_CTRL_EXTDATA); } SET_FIELD(inst->CTRL, NPCX_I3C_CTRL_EVENT, CTRL_EVENT_IBI); break; case I3C_IBI_CONTROLLER_ROLE_REQUEST: if (IS_BIT_SET(inst->STATUS, NPCX_I3C_STATUS_MRDIS)) { return -ENOTSUP; } /* The bus controller request was generate only a target with controller mode * capabilities mode */ if (GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA) != MCONFIG_CTRENA_CAPABLE) { return -ENOTSUP; } k_sem_take(&data->target_event_lock_sem, K_FOREVER); set_oper_state(dev, NPCX_I3C_OP_STATE_IBI); SET_FIELD(inst->CTRL, NPCX_I3C_CTRL_EVENT, CTRL_EVENT_CNTLR_REQ); break; case I3C_IBI_HOTJOIN: if (IS_BIT_SET(inst->STATUS, NPCX_I3C_STATUS_HJDIS)) { return -ENOTSUP; } k_sem_take(&data->target_event_lock_sem, K_FOREVER); set_oper_state(dev, NPCX_I3C_OP_STATE_IBI); inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_TGTENA); SET_FIELD(inst->CTRL, NPCX_I3C_CTRL_EVENT, CTRL_EVENT_HJ); inst->CONFIG |= BIT(NPCX_I3C_CONFIG_TGTENA); break; default: return -EINVAL; } return 0; } #ifdef CONFIG_I3C_NPCX_DMA static uint16_t npcx_i3c_target_get_mdmafb_count(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct mdma_reg *mdma_inst = config->mdma_base; if (mdma_inst->MDMA_CTCNT0 < mdma_inst->MDMA_TCNT0) { return (mdma_inst->MDMA_TCNT0 - mdma_inst->MDMA_CTCNT0); } else { return 0; } } static uint16_t npcx_i3c_target_get_mdmatb_count(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct mdma_reg *mdma_inst = config->mdma_base; if (mdma_inst->MDMA_CTCNT1 < mdma_inst->MDMA_TCNT1) { return (mdma_inst->MDMA_TCNT1 - mdma_inst->MDMA_CTCNT1); } else { return 0; } } static void npcx_i3c_target_disable_mdmafb(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_MDMAEN); mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_TC); /* W0C */ mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_SIEN); SET_FIELD(i3c_inst->DMACTRL, NPCX_I3C_DMACTRL_DMAFB, MDMA_DMAFB_DISABLE); /* Ignore DA and detect all START and STOP */ i3c_inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_MATCHSS); /* Flush the tx and rx FIFO */ i3c_inst->DATACTRL |= BIT(NPCX_I3C_DATACTRL_FLUSHTB) | BIT(NPCX_I3C_DATACTRL_FLUSHFB); } static void npcx_i3c_target_enable_mdmafb(const struct device *dev, uint8_t *buf, uint16_t len) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; /* Check MDMA disable */ if (IS_BIT_SET(mdma_inst->MDMA_CTL0, NPCX_MDMA_CTL_MDMAEN) != 0) { mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_MDMAEN); LOG_DBG("MDMAFB_EN=1 before enable"); } /* Detect a START and STOP only if the transaction * address matches the target address (STATUS.MATCHED=1). */ i3c_inst->CONFIG |= BIT(NPCX_I3C_CONFIG_MATCHSS); /* Enable manual DMA control */ SET_FIELD(i3c_inst->DMACTRL, NPCX_I3C_DMACTRL_DMAFB, MDMA_DMAFB_EN_MANUAL); /* Read Operation (MDMA CH_0) */ mdma_inst->MDMA_TCNT0 = len; /* Set MDMA transfer count */ mdma_inst->MDMA_DSTB0 = (uint32_t)buf; /* Set destination address */ mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_TC); /* W0C */ mdma_inst->MDMA_CTL0 |= BIT(NPCX_MDMA_CTL_SIEN); /* Enable stop interrupt */ mdma_inst->MDMA_CTL0 |= BIT(NPCX_MDMA_CTL_MDMAEN); /* Start DMA transfer */ } static void npcx_i3c_target_disable_mdmatb(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_MDMAEN); mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_TC); /* W0C */ mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_SIEN); SET_FIELD(i3c_inst->DMACTRL, NPCX_I3C_DMACTRL_DMATB, MDMA_DMATB_DISABLE); /* Ignore DA and detect all START and STOP */ i3c_inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_MATCHSS); /* Flush the tx and rx FIFO */ i3c_inst->DATACTRL |= BIT(NPCX_I3C_DATACTRL_FLUSHTB) | BIT(NPCX_I3C_DATACTRL_FLUSHFB); } static void npcx_i3c_target_enable_mdmatb(const struct device *dev, uint8_t *buf, uint16_t len) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *i3c_inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; /* Check MDMA disable */ if (IS_BIT_SET(mdma_inst->MDMA_CTL1, NPCX_MDMA_CTL_MDMAEN) != 0) { mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_MDMAEN); LOG_DBG("MDMATB_EN=1 before enable"); } /* Detect a START and STOP only if the transaction address matches the target address */ i3c_inst->CONFIG |= BIT(NPCX_I3C_CONFIG_MATCHSS); /* Enable DMA only for one frame. * MATCHSS must be set to 1 before selecting '0x1' for DMATB field * * In SDR DMATB is automatically cleared if MATCHED bit is set to 1 and either STOP bit * or START bit is set to 1. * * In HDR-DDR mode, DMATB is not automatically cleared. */ SET_FIELD(i3c_inst->DMACTRL, NPCX_I3C_DMACTRL_DMATB, MDMA_DMAFB_EN_ONE_FRAME); /* Write Operation (MDMA CH_1) */ mdma_inst->MDMA_TCNT1 = len; /* Set MDMA transfer count */ mdma_inst->MDMA_SRCB1 = (uint32_t)buf; /* Set source address */ mdma_inst->MDMA_CTL1 &= ~BIT(NPCX_MDMA_CTL_TC); /* W0C */ mdma_inst->MDMA_CTL1 |= BIT(NPCX_MDMA_CTL_MDMAEN); /* Start DMA transfer */ } static void npcx_i3c_target_rx_read(const struct device *dev) { struct npcx_i3c_data *data = dev->data; struct i3c_config_target *config_tgt = &data->config_target; /* Enable the DMA from bus */ npcx_i3c_target_enable_mdmafb(dev, data->mdma_rx_buf, config_tgt->max_read_len); } /* brief: Handle the end of transfer (read request or write request). * The ending signal might be either STOP or Sr. * return: -EINVAL: * 1. operation not read or write request. * 2. start or stop flag is not set. * -EBUSY: in write request, wait for mdma done. * 0: success */ static int npcx_i3c_target_xfer_end_handle(const struct device *dev) { struct npcx_i3c_data *data = dev->data; const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; struct mdma_reg *mdma_inst = config->mdma_base; const struct i3c_target_callbacks *target_cb = data->target_config->callbacks; bool is_i3c_start = IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_START); bool is_i3c_stop = IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_STOP); enum npcx_i3c_oper_state op_state = get_oper_state(dev); uint32_t cur_xfer_cnt; uint32_t timer = 0; int ret = 0; if ((op_state != NPCX_I3C_OP_STATE_WR) && (op_state != NPCX_I3C_OP_STATE_RD)) { LOG_ERR("%s: op_staste error :%d", __func__, op_state); return -EINVAL; } if ((is_i3c_start | is_i3c_stop) == 0) { LOG_ERR("%s: not the end of xfer, is_start: %d, is_stop:%d", __func__, is_i3c_start, is_i3c_stop); return -EINVAL; } /* Read request */ if (get_oper_state(dev) == NPCX_I3C_OP_STATE_RD) { npcx_i3c_target_disable_mdmatb(dev); goto out_tgt_xfer_end_hdl; } /* Write request */ /* Check rx fifo count is 0 */ if (WAIT_FOR((GET_FIELD(inst->DATACTRL, NPCX_I3C_DATACTRL_RXCOUNT) == 0), I3C_TGT_WR_REQ_WAIT_US, NULL) == false) { LOG_ERR("%s: target wr_req rxcnt timeout %d", __func__, GET_FIELD(inst->DATACTRL, NPCX_I3C_DATACTRL_RXCOUNT)); ret = -EIO; npcx_i3c_target_disable_mdmafb(dev); goto out_tgt_xfer_end_hdl; } /* Check mdma rx transfer count stability */ cur_xfer_cnt = mdma_inst->MDMA_CTCNT0; while (timer < I3C_TGT_WR_REQ_WAIT_US) { /* After the stop or Sr, the rx fifo is empty, and the last byte has been * transferred. */ if (cur_xfer_cnt != mdma_inst->MDMA_CTCNT0) { break; } /* Keep polling if the transferred count does not change */ k_busy_wait(1); timer++; cur_xfer_cnt = mdma_inst->MDMA_CTCNT0; } npcx_i3c_target_disable_mdmafb(dev); /* Disable mdma and check the final result */ if (cur_xfer_cnt == mdma_inst->MDMA_CTCNT0) { #ifdef CONFIG_I3C_TARGET_BUFFER_MODE if (target_cb && target_cb->buf_write_received_cb) { target_cb->buf_write_received_cb(data->target_config, data->mdma_rx_buf, npcx_i3c_target_get_mdmafb_count(dev)); } #endif } else { LOG_ERR("(%s) MDMA rx abnormal, force mdma stop, xfer cnt=%#x", is_i3c_start ? "Sr" : "STOP", cur_xfer_cnt); ret = -EBUSY; } out_tgt_xfer_end_hdl: /* Clear DA matched status and re-enable interrupt */ inst->STATUS = BIT(NPCX_I3C_STATUS_MATCHED); inst->INTSET = BIT(NPCX_I3C_INTSET_MATCHED); if (is_i3c_start) { set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); } return ret; } #endif /* End of CONFIG_I3C_NPCX_DMA */ static int npcx_i3c_target_tx_write(const struct device *dev, uint8_t *buf, uint16_t len, uint8_t hdr_mode) { if ((buf == NULL) || (len == 0)) { LOG_ERR("%s: Data buffer configuration failed", __func__); return -EINVAL; } if (hdr_mode != 0) { LOG_ERR("%s: HDR not supported", __func__); return -ENOSYS; } #ifdef CONFIG_I3C_NPCX_DMA npcx_i3c_target_enable_mdmatb(dev, buf, len); return npcx_i3c_target_get_mdmatb_count(dev); /* Return total bytes written */ #else LOG_ERR("%s: Support dma mode only", __func__); return -ENOSYS; #endif } static int npcx_i3c_target_register(const struct device *dev, struct i3c_target_config *cfg) { struct npcx_i3c_data *data = dev->data; data->target_config = cfg; return 0; } static int npcx_i3c_target_unregister(const struct device *dev, struct i3c_target_config *cfg) { struct npcx_i3c_data *data = dev->data; data->target_config = NULL; return 0; } static int npcx_i3c_get_scl_config(struct npcx_i3c_timing_cfg *cfg, uint32_t i3c_src_clk, uint32_t pp_baudrate_hz, uint32_t od_baudrate_hz) { uint32_t i3c_div, freq; uint32_t ppbaud, odbaud; uint32_t pplow_ns, odlow_ns; if (cfg == NULL) { LOG_ERR("Freq config NULL"); return -EINVAL; } if ((pp_baudrate_hz == 0) || (pp_baudrate_hz > I3C_SCL_PP_FREQ_MAX_MHZ) || (od_baudrate_hz == 0) || (od_baudrate_hz > I3C_SCL_OD_FREQ_MAX_MHZ)) { LOG_ERR("I3C PP_SCL should within 12.5 Mhz, input: %d", pp_baudrate_hz); LOG_ERR("I3C OD_SCL should within 4.17 Mhz, input: %d", od_baudrate_hz); return -EINVAL; } /* Fixed PPLOW = 0 to achieve 50% duty cycle */ /* pp_freq = ((f_mclkd / 2) / (PPBAUD+1)) */ freq = i3c_src_clk / 2UL; i3c_div = freq / pp_baudrate_hz; i3c_div = (i3c_div == 0UL) ? 1UL : i3c_div; if (freq / i3c_div > pp_baudrate_hz) { i3c_div++; } if (i3c_div > PPBAUD_DIV_MAX) { LOG_ERR("PPBAUD out of range"); return -EINVAL; } ppbaud = i3c_div - 1UL; freq /= i3c_div; /* Check PP low period in spec (should be the same as PPHIGH) */ pplow_ns = (uint32_t)(NSEC_PER_SEC / (2UL * freq)); if (pplow_ns < I3C_BUS_TLOW_PP_MIN_NS) { LOG_ERR("PPLOW ns out of spec"); return -EINVAL; } /* Fixed odhpp = 1 configuration */ /* odFreq = (2*freq) / (ODBAUD + 2), 1 <= ODBAUD <= 255 */ i3c_div = (2UL * freq) / od_baudrate_hz; i3c_div = i3c_div < 2UL ? 2UL : i3c_div; if ((2UL * freq / i3c_div) > od_baudrate_hz) { i3c_div++; } odbaud = i3c_div - 2UL; freq = (2UL * freq) / i3c_div; /* For I2C usage in the future */ /* Check OD low period in spec */ odlow_ns = (odbaud + 1UL) * pplow_ns; if (odlow_ns < I3C_BUS_TLOW_OD_MIN_NS) { LOG_ERR("ODBAUD ns out of spec"); return -EINVAL; } cfg->pplow = 0; cfg->odhpp = 1; cfg->ppbaud = ppbaud; cfg->odbaud = odbaud; return 0; } static int npcx_i3c_freq_init(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; const struct device *const clk_dev = config->clock_dev; struct i3c_config_controller *ctrl_config = &data->common.ctrl_config; uint32_t scl_pp = ctrl_config->scl.i3c; uint32_t scl_od = config->clocks.i3c_od_scl_hz; struct npcx_i3c_timing_cfg timing_cfg; uint32_t mclkd; int ret; ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t)&config->clock_subsys, &mclkd); if (ret != 0x0) { LOG_ERR("Get I3C source clock fail %d", ret); return -EINVAL; } LOG_DBG("MCLKD: %d", mclkd); LOG_DBG("SCL_PP_FEQ MAX: %d", I3C_SCL_PP_FREQ_MAX_MHZ); LOG_DBG("SCL_OD_FEQ MAX: %d", I3C_SCL_OD_FREQ_MAX_MHZ); LOG_DBG("scl_pp: %d", scl_pp); LOG_DBG("scl_od: %d", scl_od); LOG_DBG("hdr: %d", ctrl_config->supported_hdr); /* MCLKD = MCLK / I3C_DIV(1 or 2) * MCLKD must between 40 mhz to 50 mhz. */ if (mclkd == MCLKD_FREQ_MHZ(40)) { /* Set default I3C_SCL configuration */ timing_cfg = npcx_def_speed_cfg[NPCX_I3C_BUS_SPEED_40MHZ]; } else if (mclkd == MCLKD_FREQ_MHZ(45)) { /* Set default I3C_SCL configuration */ timing_cfg = npcx_def_speed_cfg[NPCX_I3C_BUS_SPEED_45MHZ]; } else if (mclkd == MCLKD_FREQ_MHZ(48)) { /* Set default I3C_SCL configuration */ timing_cfg = npcx_def_speed_cfg[NPCX_I3C_BUS_SPEED_48MHZ]; } else if (mclkd == MCLKD_FREQ_MHZ(50)) { /* Set default I3C_SCL configuration */ timing_cfg = npcx_def_speed_cfg[NPCX_I3C_BUS_SPEED_50MHZ]; } else { LOG_ERR("Unsupported MCLKD freq for %s.", dev->name); return -EINVAL; } ret = npcx_i3c_get_scl_config(&timing_cfg, mclkd, scl_pp, scl_od); if (ret != 0x0) { LOG_ERR("Adjust I3C frequency fail"); return -EINVAL; } /* Apply SCL_PP and SCL_OD */ SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_PPBAUD, timing_cfg.ppbaud); SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_PPLOW, timing_cfg.pplow); SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_ODBAUD, timing_cfg.odbaud); if (timing_cfg.odhpp != 0) { inst->MCONFIG |= BIT(NPCX_I3C_MCONFIG_ODHPP); } else { inst->MCONFIG &= ~BIT(NPCX_I3C_MCONFIG_ODHPP); } SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_I2CBAUD, I3C_BUS_I2C_BAUD_RATE_FAST_MODE); LOG_DBG("ppbaud: %d", GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_PPBAUD)); LOG_DBG("odbaud: %d", GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_ODBAUD)); LOG_DBG("pplow: %d", GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_PPLOW)); LOG_DBG("odhpp: %d", IS_BIT_SET(inst->MCONFIG, NPCX_I3C_MCONFIG_ODHPP)); LOG_DBG("i2cbaud: %d", GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_I2CBAUD)); return 0; } static int npcx_i3c_apply_cntlr_config(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; const struct device *const clk_dev = config->clock_dev; int idx_module = GET_MODULE_ID(config->instance_id); uint32_t apb4_rate; uint8_t bamatch; int ret; /* I3C module mdma cotroller or target mode select */ npcx_i3c_target_sel(idx_module, false); /* Disable all interrupts */ npcx_i3c_interrupt_all_disable(inst); /* Initial baudrate. PPLOW=1, PPBAUD, ODHPP=1, ODBAUD */ if (npcx_i3c_freq_init(dev) != 0x0) { return -EINVAL; } /* Enable external high-keeper */ SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_HKEEP, MCONFIG_HKEEP_EXT_SDA_SCL); /* Enable open-drain stop */ inst->MCONFIG |= BIT(NPCX_I3C_MCONFIG_ODSTOP); /* Enable timeout */ inst->MCONFIG &= ~BIT(NPCX_I3C_MCONFIG_DISTO); /* Flush tx and tx FIFO buffer */ npcx_i3c_fifo_flush(inst); /* Set bus available match value in target register */ ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t)&config->ref_clk_subsys, &apb4_rate); LOG_DBG("APB4_CLK: %d", apb4_rate); if (ret != 0x0) { LOG_ERR("%s: Get APB4 source clock fail %d", __func__, ret); return -EINVAL; } bamatch = get_bus_available_match_val(apb4_rate); LOG_DBG("BAMATCH: %d", bamatch); SET_FIELD(inst->CONFIG, NPCX_I3C_CONFIG_BAMATCH, bamatch); return 0; } static int npcx_i3c_apply_target_config(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_config_target *config_target = &data->config_target; struct i3c_reg *inst = config->base; const struct device *const clk_dev = config->clock_dev; uint32_t apb4_rate; uint8_t bamatch; int idx_module = GET_MODULE_ID(config->instance_id); int ret; uint64_t pid; /* I3C module mdma cotroller or target mode select */ npcx_i3c_target_sel(idx_module, true); /* Set bus available match value in target register */ ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t)&config->ref_clk_subsys, &apb4_rate); LOG_DBG("APB4_CLK: %d", apb4_rate); if (ret != 0x0) { LOG_ERR("%s: Get APB4 source clock fail %d", __func__, ret); return -EINVAL; } bamatch = get_bus_available_match_val(apb4_rate); LOG_DBG("BAMATCH: %d", bamatch); SET_FIELD(inst->CONFIG, NPCX_I3C_CONFIG_BAMATCH, bamatch); /* Set Provisional ID */ pid = config_target->pid; /* PID[47:33] MIPI manufacturer ID */ SET_FIELD(inst->VENDORID, NPCX_I3C_VENDORID_VID, (uint32_t)GET_PID_VENDOR_ID(pid)); /* PID[32] Vendor fixed value(0) or random value(1) */ if (config_target->pid_random) { inst->CONFIG |= BIT(NPCX_I3C_CONFIG_IDRAND); } else { inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_IDRAND); } /* PID[31:0] vendor fixed value */ inst->PARTNO = (uint32_t)GET_PID_PARTNO(pid); LOG_DBG("pid: %#llx", pid); LOG_DBG("vendro id: %#x", (uint32_t)GET_PID_VENDOR_ID(pid)); LOG_DBG("id type: %d", (uint32_t)GET_PID_ID_TYP(pid)); LOG_DBG("partno: %#x", (uint32_t)GET_PID_PARTNO(pid)); SET_FIELD(inst->IDEXT, NPCX_I3C_IDEXT_DCR, config_target->dcr); SET_FIELD(inst->IDEXT, NPCX_I3C_IDEXT_BCR, config_target->bcr); SET_FIELD(inst->CONFIG, NPCX_I3C_CONFIG_SADDR, config_target->static_addr); SET_FIELD(inst->CONFIG, NPCX_I3C_CONFIG_HDRCMD, CFG_HDRCMD_RD_FROM_FIFIO); SET_FIELD(inst->MAXLIMITS, NPCX_I3C_MAXLIMITS_MAXRD, (config_target->max_read_len) & 0xfff); SET_FIELD(inst->MAXLIMITS, NPCX_I3C_MAXLIMITS_MAXWR, (config_target->max_write_len) & 0xfff); /* Ignore DA and detect all START and STOP */ inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_MATCHSS); /* Enable the target interrupt events */ npcx_i3c_enable_target_interrupt(dev, true); return 0; } static void npcx_i3c_dev_init(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_reg *inst = config->base; struct i3c_config_controller *config_cntlr = &data->common.ctrl_config; struct i3c_config_target *config_target = &data->config_target; int idx_module = GET_MODULE_ID(config->instance_id); /* Reset I3C module */ reset_line_toggle_dt(&config->reset); if (I3C_BCR_DEVICE_ROLE(config_target->bcr) == I3C_BCR_DEVICE_ROLE_I3C_CONTROLLER_CAPABLE) { npcx_i3c_apply_cntlr_config(dev); npcx_i3c_apply_target_config(dev); if (config_cntlr->is_secondary) { /* Secondary controller enable, so boot as a target */ SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA, MCONFIG_CTRENA_CAPABLE); inst->CONFIG |= BIT(NPCX_I3C_CONFIG_TGTENA); /* Target mode enable */ } else { npcx_i3c_target_sel(idx_module, false); /* Set mdma as controlelr */ /* Primary Controller enable */ SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA, MCONFIG_CTRENA_ON); } } else { npcx_i3c_apply_target_config(dev); SET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA, MCONFIG_CTRENA_OFF); /* Controller mode off */ inst->CONFIG |= BIT(NPCX_I3C_CONFIG_TGTENA); /* Target mode enable */ } } static int npcx_i3c_configure(const struct device *dev, enum i3c_config_type type, void *config) { struct npcx_i3c_data *dev_data = dev->data; struct i3c_config_controller *config_cntlr; struct i3c_config_target *config_target; if (config == NULL) { LOG_ERR("%s: config is NULL", __func__); return -EINVAL; } if (type == I3C_CONFIG_CONTROLLER) { config_cntlr = config; /* * Check for valid configuration parameters. * Currently, must be the primary controller. */ if (config_cntlr->scl.i3c == 0U) { LOG_ERR("%s: configure controller failed", __func__); return -EINVAL; } /* Save requested config to dev */ (void)memcpy(&dev_data->common.ctrl_config, config_cntlr, sizeof(*config_cntlr)); return npcx_i3c_apply_cntlr_config(dev); } else if (type == I3C_CONFIG_TARGET) { config_target = config; if (config_target->pid == 0) { LOG_ERR("%s: configure target failed", __func__); return -EINVAL; } return npcx_i3c_apply_target_config(dev); } LOG_ERR("Config type not supported, %d", type); return -EINVAL; } static int npcx_i3c_config_get(const struct device *dev, enum i3c_config_type type, void *config) { struct npcx_i3c_data *data = dev->data; if (config == NULL) { return -EINVAL; } if (type == I3C_CONFIG_CONTROLLER) { (void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config)); } else if (type == I3C_CONFIG_TARGET) { (void)memcpy(config, &data->config_target, sizeof(data->config_target)); } else { return -EINVAL; } return 0; } static void npcx_i3c_target_isr(const struct device *dev) { struct npcx_i3c_data *data = dev->data; const struct npcx_i3c_config *config = dev->config; struct i3c_config_target *config_tgt = &data->config_target; struct i3c_target_config *target_config = data->target_config; struct i3c_reg *inst = config->base; const struct i3c_target_callbacks *target_cb = data->target_config->callbacks; #ifdef CONFIG_I3C_NPCX_DMA struct mdma_reg *mdma_inst = config->mdma_base; /* Check mdma read end (for write request) */ if (IS_BIT_SET(mdma_inst->MDMA_CTL0, NPCX_MDMA_CTL_TC)) { /* Disable target read operation */ npcx_i3c_target_disable_mdmafb(dev); /* End of mdma read (write request) */ if (get_oper_state(dev) == NPCX_I3C_OP_STATE_WR) { #ifdef CONFIG_I3C_TARGET_BUFFER_MODE if ((target_cb != NULL) && (target_cb->buf_write_received_cb != NULL)) { target_cb->buf_write_received_cb( data->target_config, data->mdma_rx_buf, npcx_i3c_target_get_mdmafb_count(dev)); } #endif } else { LOG_ERR("%s: write request TC=1, operation state error, %d", __func__, data->oper_state); } set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); } #endif /* CONFIG_I3C_NPCX_DMA */ while (inst->INTMASKED) { /* Check STOP detected */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_STOP)) { if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_START)) { inst->STATUS = BIT(NPCX_I3C_STATUS_START); } #ifdef CONFIG_I3C_NPCX_DMA /* The end of xfer is a stop. * For write request: check whether mdma TC is done or still busy. * For read request: disable the mdma operation. */ if ((get_oper_state(dev) == NPCX_I3C_OP_STATE_WR) || (get_oper_state(dev) == NPCX_I3C_OP_STATE_RD)) { if (npcx_i3c_target_xfer_end_handle(dev) != 0) { LOG_ERR("xfer end handle failed after stop, op state=%d", get_oper_state(dev)); } } inst->STATUS = BIT(NPCX_I3C_STATUS_STOP); #endif /* Notify upper layer a STOP condition received */ if ((target_cb != NULL) && (target_cb->stop_cb != NULL)) { target_cb->stop_cb(data->target_config); } /* Clear DA matched status and re-enable interrupt */ inst->STATUS = BIT(NPCX_I3C_STATUS_MATCHED); inst->INTSET = BIT(NPCX_I3C_INTSET_MATCHED); set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); } /* Check START or Sr detected */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_START)) { /* The end of xfer is a Sr */ if ((get_oper_state(dev) == NPCX_I3C_OP_STATE_WR) || (get_oper_state(dev) == NPCX_I3C_OP_STATE_RD)) { if (-EBUSY == npcx_i3c_target_xfer_end_handle(dev)) { return; } } inst->STATUS = BIT(NPCX_I3C_STATUS_START); } if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_TGTRST)) { inst->STATUS = BIT(NPCX_I3C_STATUS_TGTRST); } /* Check error or warning has occurred */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_ERRWARN)) { LOG_ERR("%s: Error %#x", __func__, inst->ERRWARN); inst->ERRWARN = inst->ERRWARN; } /* Check incoming header matched target dynamic address */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_MATCHED)) { if (get_oper_state(dev) != NPCX_I3C_OP_STATE_IBI) { /* The current bus request is an SDR mode read or write */ if (IS_BIT_SET(inst->STATUS, NPCX_I3C_STATUS_STREQRD)) { /* SDR read request */ set_oper_state(dev, NPCX_I3C_OP_STATE_RD); /* Emit read request callback */ #if CONFIG_I3C_TARGET_BUFFER_MODE /* It will be too late to enable mdma here, use * target_tx_write() to write tx data into fifo before * controller send read request. */ if ((target_cb != NULL) && (target_cb->buf_read_requested_cb != NULL)) { target_cb->buf_read_requested_cb( data->target_config, NULL, NULL, NULL); } #endif } else { /* SDR write request */ set_oper_state(dev, NPCX_I3C_OP_STATE_WR); /* Emit write request callback */ if ((target_cb != NULL) && (target_cb->write_requested_cb != NULL)) { target_cb->write_requested_cb(data->target_config); } npcx_i3c_target_rx_read(dev); } } /* If CONFIG.MATCHSS=1, MATCHED bit must remain 1 to detect next start * or stop. * * Clear the status bit in STOP or START handler. */ if (IS_BIT_SET(inst->CONFIG, NPCX_I3C_CONFIG_MATCHSS)) { inst->INTCLR = BIT(NPCX_I3C_INTCLR_MATCHED); } else { inst->STATUS = BIT(NPCX_I3C_STATUS_MATCHED); } } /* Check dynamic address changed */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_DACHG)) { inst->STATUS = BIT(NPCX_I3C_STATUS_DACHG); if (IS_BIT_SET(inst->DYNADDR, NPCX_I3C_DYNADDR_DAVALID)) { if (target_config != NULL) { config_tgt->dynamic_addr = GET_FIELD(inst->DYNADDR, NPCX_I3C_DYNADDR_DADDR); } } } /* CCC 'not' automatically handled was received */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_CCC)) { inst->STATUS = BIT(NPCX_I3C_STATUS_CCC); } /* HDR command, address match */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_HDRMATCH)) { inst->STATUS = BIT(NPCX_I3C_STATUS_HDRMATCH); } /* CCC handled (handled by IP) */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_CHANDLED)) { inst->STATUS = BIT(NPCX_I3C_STATUS_CHANDLED); } /* Event requested. IBI, hot-join, bus control */ if (IS_BIT_SET(inst->INTMASKED, NPCX_I3C_INTMASKED_EVENT)) { inst->STATUS = BIT(NPCX_I3C_STATUS_EVENT); if (GET_FIELD(inst->STATUS, NPCX_I3C_STATUS_EVDET) == STATUS_EVDET_REQ_SENT_ACKED) { k_sem_give(&data->target_event_lock_sem); } } } /* Secondary controller (Controller register). * Check I3C now bus controller. * Disable target mode if target switch to controller mode success. */ if (IS_BIT_SET(inst->MINTMASKED, NPCX_I3C_MINTMASKED_NOWCNTLR)) { inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_NOWCNTLR); /* W1C */ inst->CONFIG &= ~BIT(NPCX_I3C_CONFIG_TGTENA); /* Disable target mode */ } } static void npcx_i3c_isr(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct i3c_reg *inst = config->base; if (IS_BIT_SET(inst->CONFIG, NPCX_I3C_CONFIG_TGTENA)) { npcx_i3c_target_isr(dev); return; } #ifdef CONFIG_I3C_NPCX_DMA struct mdma_reg *mdma_inst = config->mdma_base; /* Controller write end */ if (IS_BIT_SET(inst->MSTATUS, NPCX_I3C_MSTATUS_COMPLETE)) { inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_COMPLETE); /* W1C */ /* MDMA write */ if (get_oper_state(dev) == NPCX_I3C_OP_STATE_WR) { i3c_ctrl_notify(dev); return; } } /* Controller read end */ if (IS_BIT_SET(mdma_inst->MDMA_CTL0, NPCX_MDMA_CTL_TC)) { mdma_inst->MDMA_CTL0 &= ~BIT(NPCX_MDMA_CTL_TC); /* W0C */ /* MDMA read */ if (get_oper_state(dev) == NPCX_I3C_OP_STATE_RD) { i3c_ctrl_notify(dev); return; } } #endif /* CONFIG_I3C_NPCX_DMA */ #ifdef CONFIG_I3C_USE_IBI int ret; /* 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); /* Clear TGTSTART interrupt */ inst->MSTATUS = BIT(NPCX_I3C_MSTATUS_TGTSTART); /* Handle IBI in workqueue */ ret = i3c_ibi_work_enqueue_cb(dev, npcx_i3c_ibi_work); if (ret < 0) { LOG_ERR("Enqueuing ibi work fail, ret %d", ret); inst->MINTSET = BIT(NPCX_I3C_MINTSET_TGTSTART); } } #endif /* CONFIG_I3C_USE_IBI */ } static int npcx_i3c_init(const struct device *dev) { const struct npcx_i3c_config *config = dev->config; struct npcx_i3c_data *data = dev->data; struct i3c_config_controller *config_cntlr = &data->common.ctrl_config; const struct device *const clk_dev = config->clock_dev; struct i3c_reg *inst = config->base; int ret; /* Check clock device ready */ if (!device_is_ready(clk_dev)) { LOG_ERR("%s Clk device not ready", clk_dev->name); return -ENODEV; } /* Set I3C_PD operational */ ret = clock_control_on(clk_dev, (clock_control_subsys_t)&config->clock_subsys); if (ret < 0) { LOG_ERR("Turn on I3C clock fail %d", ret); return ret; } #ifdef CONFIG_I3C_NPCX_DMA ret = clock_control_on(clk_dev, (clock_control_subsys_t)&config->mdma_clk_subsys); if (ret < 0) { LOG_ERR("Turn on I3C MDMA clock fail %d", ret); return ret; } #endif /* Apply pin-muxing */ ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); if (ret != 0) { LOG_ERR("Apply pinctrl fail %d", ret); return ret; } /* Lock initial */ k_mutex_init(&data->lock_mutex); k_sem_init(&data->sync_sem, 0, 1); k_sem_init(&data->ibi_lock_sem, 1, 1); k_sem_init(&data->target_lock_sem, 1, 1); k_sem_init(&data->target_event_lock_sem, 1, 1); ret = i3c_addr_slots_init(dev); if (ret != 0) { LOG_ERR("Addr slots init fail %d", ret); return ret; } /* Set controller default configuration */ config_cntlr->supported_hdr = I3C_MSG_HDR_DDR; /* HDR-DDR mode is supported. */ config_cntlr->scl.i3c = config->clocks.i3c_pp_scl_hz; /* Set I3C frequency */ /* Initial I3C device as controller or target */ npcx_i3c_dev_init(dev); if (GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA) == MCONFIG_CTRENA_ON) { /* Just in case the bus is not in idle. */ ret = npcx_i3c_recover_bus(dev); if (ret != 0) { LOG_ERR("Apply i3c_recover_bus() fail %d", ret); return ret; } } /* Configure interrupt */ config->irq_config_func(dev); /* Initialize driver status machine */ set_oper_state(dev, NPCX_I3C_OP_STATE_IDLE); /* Check I3C is controller mode and target device exist in device tree */ if ((config->common.dev_list.num_i3c > 0) && GET_FIELD(inst->MCONFIG, NPCX_I3C_MCONFIG_CTRENA) == MCONFIG_CTRENA_ON) { /* Perform bus initialization */ ret = i3c_bus_init(dev, &config->common.dev_list); if (ret != 0) { LOG_ERR("Apply i3c_bus_init() fail %d", ret); return ret; } } return 0; } static DEVICE_API(i3c, npcx_i3c_driver_api) = { .configure = npcx_i3c_configure, .config_get = npcx_i3c_config_get, .recover_bus = npcx_i3c_recover_bus, .do_daa = npcx_i3c_do_daa, .do_ccc = npcx_i3c_do_ccc, .i3c_device_find = npcx_i3c_device_find, .i3c_xfers = npcx_i3c_transfer, .target_tx_write = npcx_i3c_target_tx_write, .target_register = npcx_i3c_target_register, .target_unregister = npcx_i3c_target_unregister, #ifdef CONFIG_I3C_USE_IBI .ibi_enable = npcx_i3c_ibi_enable, .ibi_disable = npcx_i3c_ibi_disable, .ibi_raise = npcx_i3c_target_ibi_raise, #endif #ifdef CONFIG_I3C_RTIO .iodev_submit = i3c_iodev_submit_fallback, #endif }; #define DT_INST_TGT_PID_PROP_OR(id, prop, idx) \ COND_CODE_1(DT_INST_PROP_HAS_IDX(id, prop, idx), (DT_INST_PROP_BY_IDX(id, prop, idx)), (0)) #define DT_INST_TGT_PID_RAND_PROP_OR(id, prop, idx) \ COND_CODE_1(DT_INST_PROP_HAS_IDX(id, prop, idx), \ IS_BIT_SET(DT_INST_PROP_BY_IDX(id, prop, 0), 0), (0)) #define I3C_NPCX_DEVICE(id) \ PINCTRL_DT_INST_DEFINE(id); \ static void npcx_i3c_config_func_##id(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), npcx_i3c_isr, \ DEVICE_DT_INST_GET(id), 0); \ irq_enable(DT_INST_IRQN(id)); \ }; \ static struct i3c_device_desc npcx_i3c_device_array_##id[] = I3C_DEVICE_ARRAY_DT_INST(id); \ static struct i3c_i2c_device_desc npcx_i3c_i2c_device_array_##id[] = \ I3C_I2C_DEVICE_ARRAY_DT_INST(id); \ static const struct npcx_i3c_config npcx_i3c_config_##id = { \ .base = (struct i3c_reg *)DT_INST_REG_ADDR(id), \ .clock_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE), \ .reset = RESET_DT_SPEC_INST_GET(id), \ .clock_subsys = NPCX_DT_CLK_CFG_ITEM_BY_NAME(id, mclkd), \ .ref_clk_subsys = NPCX_DT_CLK_CFG_ITEM_BY_NAME(id, apb4), \ .irq_config_func = npcx_i3c_config_func_##id, \ .common.dev_list.i3c = npcx_i3c_device_array_##id, \ .common.dev_list.num_i3c = ARRAY_SIZE(npcx_i3c_device_array_##id), \ .common.dev_list.i2c = npcx_i3c_i2c_device_array_##id, \ .common.dev_list.num_i2c = ARRAY_SIZE(npcx_i3c_i2c_device_array_##id), \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id), \ .instance_id = DT_INST_PROP(id, instance_id), \ .clocks.i3c_pp_scl_hz = DT_INST_PROP_OR(id, i3c_scl_hz, 0), \ .clocks.i3c_od_scl_hz = DT_INST_PROP_OR(id, i3c_od_scl_hz, 0), \ IF_ENABLED(CONFIG_I3C_NPCX_DMA, ( \ .mdma_clk_subsys = NPCX_DT_CLK_CFG_ITEM_BY_IDX(id, 2), \ )) \ IF_ENABLED(CONFIG_I3C_NPCX_DMA, ( \ .mdma_base = (struct mdma_reg *)DT_INST_REG_ADDR_BY_IDX(id, 1), \ )) }; \ static struct npcx_i3c_data npcx_i3c_data_##id = { \ .common.ctrl_config.is_secondary = DT_INST_PROP_OR(id, secondary, false), \ .config_target.static_addr = DT_INST_PROP_OR(id, static_address, 0), \ .config_target.pid = ((uint64_t)DT_INST_TGT_PID_PROP_OR(id, tgt_pid, 0) << 32) | \ DT_INST_TGT_PID_PROP_OR(id, tgt_pid, 1), \ .config_target.pid_random = DT_INST_TGT_PID_RAND_PROP_OR(id, tgt_pid, 0), \ .config_target.bcr = DT_INST_PROP(id, bcr), \ .config_target.dcr = DT_INST_PROP_OR(id, dcr, 0), \ .config_target.max_read_len = DT_INST_PROP_OR(id, maximum_read, 0), \ .config_target.max_write_len = DT_INST_PROP_OR(id, maximum_write, 0), \ .config_target.supported_hdr = false, \ }; \ DEVICE_DT_INST_DEFINE(id, npcx_i3c_init, NULL, &npcx_i3c_data_##id, &npcx_i3c_config_##id, \ POST_KERNEL, CONFIG_I3C_CONTROLLER_INIT_PRIORITY, \ &npcx_i3c_driver_api); DT_INST_FOREACH_STATUS_OKAY(I3C_NPCX_DEVICE)