|  | /* | 
|  | * Copyright (c) 2016 Freescale Semiconductor, Inc. | 
|  | * Copyright (c) 2019, 2025 NXP | 
|  | * Copyright (c) 2022 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nxp_mcux_i3c | 
|  |  | 
|  | #include <string.h> | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/sys/sys_io.h> | 
|  |  | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  | #include <zephyr/drivers/i3c.h> | 
|  | #include <zephyr/spinlock.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  |  | 
|  | /* | 
|  | * This is from NXP HAL which contains register bits macros | 
|  | * which are used in this driver. | 
|  | */ | 
|  | #include <fsl_i3c.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(i3c_mcux, CONFIG_I3C_MCUX_LOG_LEVEL); | 
|  |  | 
|  | #define I3C_MCTRL_REQUEST_NONE			I3C_MCTRL_REQUEST(0) | 
|  | #define I3C_MCTRL_REQUEST_EMIT_START_ADDR	I3C_MCTRL_REQUEST(1) | 
|  | #define I3C_MCTRL_REQUEST_EMIT_STOP		I3C_MCTRL_REQUEST(2) | 
|  | #define I3C_MCTRL_REQUEST_IBI_ACK_NACK		I3C_MCTRL_REQUEST(3) | 
|  | #define I3C_MCTRL_REQUEST_PROCESS_DAA		I3C_MCTRL_REQUEST(4) | 
|  | #define I3C_MCTRL_REQUEST_FORCE_EXIT		I3C_MCTRL_REQUEST(6) | 
|  | #define I3C_MCTRL_REQUEST_AUTO_IBI		I3C_MCTRL_REQUEST(7) | 
|  |  | 
|  | #define I3C_MCTRL_IBIRESP_ACK			I3C_MCTRL_IBIRESP(0) | 
|  | #define I3C_MCTRL_IBIRESP_ACK_AUTO		I3C_MCTRL_IBIRESP(0) | 
|  | #define I3C_MCTRL_IBIRESP_NACK			I3C_MCTRL_IBIRESP(1) | 
|  | #define I3C_MCTRL_IBIRESP_ACK_WITH_BYTE		I3C_MCTRL_IBIRESP(2) | 
|  | #define I3C_MCTRL_IBIRESP_MANUAL		I3C_MCTRL_IBIRESP(3) | 
|  |  | 
|  | #define I3C_MCTRL_TYPE_I3C			I3C_MCTRL_TYPE(0) | 
|  | #define I3C_MCTRL_TYPE_I2C			I3C_MCTRL_TYPE(1) | 
|  |  | 
|  | #define I3C_MCTRL_DIR_WRITE			I3C_MCTRL_DIR(0) | 
|  | #define I3C_MCTRL_DIR_READ			I3C_MCTRL_DIR(1) | 
|  |  | 
|  | #define I3C_MSTATUS_STATE_IDLE			I3C_MSTATUS_STATE(0) | 
|  | #define I3C_MSTATUS_STATE_SLVREQ		I3C_MSTATUS_STATE(1) | 
|  | #define I3C_MSTATUS_STATE_MSGSDR		I3C_MSTATUS_STATE(2) | 
|  | #define I3C_MSTATUS_STATE_NORMACT		I3C_MSTATUS_STATE(3) | 
|  | #define I3C_MSTATUS_STATE_MSGDDR		I3C_MSTATUS_STATE(4) | 
|  | #define I3C_MSTATUS_STATE_DAA			I3C_MSTATUS_STATE(5) | 
|  | #define I3C_MSTATUS_STATE_IBIACK		I3C_MSTATUS_STATE(6) | 
|  | #define I3C_MSTATUS_STATE_IBIRCV		I3C_MSTATUS_STATE(7) | 
|  |  | 
|  | #define I3C_MSTATUS_IBITYPE_NONE		I3C_MSTATUS_IBITYPE(0) | 
|  | #define I3C_MSTATUS_IBITYPE_IBI			I3C_MSTATUS_IBITYPE(1) | 
|  | #define I3C_MSTATUS_IBITYPE_MR			I3C_MSTATUS_IBITYPE(2) | 
|  | #define I3C_MSTATUS_IBITYPE_HJ			I3C_MSTATUS_IBITYPE(3) | 
|  |  | 
|  | #define I3C_MAX_STOP_RETRIES 5 | 
|  |  | 
|  | #define I3C_TRANSFER_TIMEOUT_MSEC					\ | 
|  | COND_CODE_0(CONFIG_I3C_NXP_TRANSFER_TIMEOUT, (K_FOREVER),	\ | 
|  | (K_MSEC(CONFIG_I3C_NXP_TRANSFER_TIMEOUT))) | 
|  |  | 
|  | struct mcux_i3c_config { | 
|  | /** Common I3C Driver Config */ | 
|  | struct i3c_driver_config common; | 
|  |  | 
|  | /** Pointer to controller registers. */ | 
|  | I3C_Type *base; | 
|  |  | 
|  | /** Pointer to the clock device. */ | 
|  | const struct device *clock_dev; | 
|  |  | 
|  | /** Clock control subsys related struct. */ | 
|  | clock_control_subsys_t clock_subsys; | 
|  |  | 
|  | /** Pointer to pin control device. */ | 
|  | const struct pinctrl_dev_config *pincfg; | 
|  |  | 
|  | /** Interrupt configuration function. */ | 
|  | void (*irq_config_func)(const struct device *dev); | 
|  |  | 
|  | /** Disable open drain high push pull */ | 
|  | bool disable_open_drain_high_pp; | 
|  | }; | 
|  |  | 
|  | struct mcux_i3c_data { | 
|  | /** Common I3C Driver Data */ | 
|  | struct i3c_driver_data common; | 
|  |  | 
|  | /** Mutex to serialize access */ | 
|  | struct k_mutex lock; | 
|  |  | 
|  | /** Semaphore to synchronize data transfers */ | 
|  | struct k_sem device_sync_sem; | 
|  |  | 
|  | /** Condvar for waiting for bus to be in IDLE state */ | 
|  | struct k_condvar condvar; | 
|  |  | 
|  | /** I3C open drain clock frequency in Hz. */ | 
|  | uint32_t i3c_od_scl_hz; | 
|  |  | 
|  | #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 | 
|  | }; | 
|  |  | 
|  | uint32_t merrwarn_reg; | 
|  | static struct k_spinlock lock; | 
|  |  | 
|  | /** | 
|  | * @brief Read a register and test for bit matches with timeout. | 
|  | * | 
|  | * Please be aware that this uses @see k_busy_wait. | 
|  | * | 
|  | * @param reg Pointer to 32-bit Register. | 
|  | * @param mask Mask to the register value. | 
|  | * @param match Value to match for masked register value. | 
|  | * @param timeout_us Timeout in microsecond before bailing out. | 
|  | * | 
|  | * @retval 0 If masked register value matches before time out. | 
|  | * @retval -ETIMEDOUT Timedout without matching. | 
|  | */ | 
|  | static int reg32_poll_timeout(volatile uint32_t *reg, | 
|  | uint32_t mask, uint32_t match, | 
|  | uint32_t timeout_us) | 
|  | { | 
|  | /* | 
|  | * These polling checks are typically satisfied | 
|  | * quickly (some sub-microseconds) so no extra | 
|  | * delay between checks. | 
|  | */ | 
|  | if (!WAIT_FOR((sys_read32((mm_reg_t)reg) & mask) == match, timeout_us, /*nop*/)) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Update register value. | 
|  | * | 
|  | * @param reg Pointer to 32-bit Register. | 
|  | * @param mask Mask to the register value. | 
|  | * @param update Value to be updated in register. | 
|  | */ | 
|  | static inline void reg32_update(volatile uint32_t *reg, | 
|  | uint32_t mask, uint32_t update) | 
|  | { | 
|  | uint32_t val = sys_read32((mem_addr_t)reg); | 
|  |  | 
|  | val &= ~mask; | 
|  | val |= (update & mask); | 
|  |  | 
|  | sys_write32(val, (mem_addr_t)reg); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Test if masked register value has certain value. | 
|  | * | 
|  | * @param reg Pointer to 32-bit register. | 
|  | * @param mask Mask to test. | 
|  | * @param match Value to match. | 
|  | * | 
|  | * @return True if bits in @p mask mask matches @p match, false otherwise. | 
|  | */ | 
|  | static inline bool reg32_test_match(volatile uint32_t *reg, | 
|  | uint32_t mask, uint32_t match) | 
|  | { | 
|  | uint32_t val = sys_read32((mem_addr_t)reg); | 
|  |  | 
|  | return (val & mask) == match; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Test if masked register value is the same as the mask. | 
|  | * | 
|  | * @param reg Pointer to 32-bit register. | 
|  | * @param mask Mask to test. | 
|  | * | 
|  | * @return True if bits in @p mask are all set, false otherwise. | 
|  | */ | 
|  | static inline bool reg32_test(volatile uint32_t *reg, uint32_t mask) | 
|  | { | 
|  | return reg32_test_match(reg, mask, mask); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Disable all interrupts. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * | 
|  | * @return Previous enabled interrupts. | 
|  | */ | 
|  | static uint32_t mcux_i3c_interrupt_disable(I3C_Type *base) | 
|  | { | 
|  | uint32_t intmask = base->MINTSET; | 
|  |  | 
|  | base->MINTCLR = intmask; | 
|  |  | 
|  | return intmask; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Enable interrupts according to mask. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Interrupts to be enabled. | 
|  | * | 
|  | */ | 
|  | static void mcux_i3c_interrupt_enable(I3C_Type *base, uint32_t mask) | 
|  | { | 
|  | base->MINTSET = mask; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Check if there are any errors. | 
|  | * | 
|  | * @retval errors reported or 0 if no errors. | 
|  | */ | 
|  | static uint32_t mcux_i3c_has_error(void) | 
|  | { | 
|  | uint32_t ret = 0; | 
|  |  | 
|  | k_spinlock_key_t key = k_spin_lock(&lock); | 
|  |  | 
|  | if (merrwarn_reg) { | 
|  | /* Read and clear */ | 
|  | ret = merrwarn_reg; | 
|  | merrwarn_reg = 0; | 
|  | } | 
|  |  | 
|  | k_spin_unlock(&lock, key); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Test if certain bits are set in MSTATUS. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be tested. | 
|  | * | 
|  | * @retval True if @p mask bits are set. | 
|  | * @retval False if @p mask bits are not set. | 
|  | */ | 
|  | static inline bool mcux_i3c_status_is_set(I3C_Type *base, uint32_t mask) | 
|  | { | 
|  | return reg32_test(&base->MSTATUS, mask); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Spin wait for MSTATUS bit to be set. | 
|  | * | 
|  | * This spins forever for the bits to be set. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be tested. | 
|  | */ | 
|  | static inline void mcux_i3c_status_wait(I3C_Type *base, uint32_t mask) | 
|  | { | 
|  | /* Wait for bits to be set */ | 
|  | while (!mcux_i3c_status_is_set(base, mask)) { | 
|  | k_busy_wait(1); | 
|  | }; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Wait for MSTATUS bits to be set with time out. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be tested. | 
|  | * @param timeout_us Timeout in microsecond before bailing out. | 
|  | * | 
|  | * @retval 0 If bits are set before time out. | 
|  | * @retval -ETIMEDOUT | 
|  | */ | 
|  | static inline int mcux_i3c_status_wait_timeout(I3C_Type *base, uint32_t mask, | 
|  | uint32_t timeout_us) | 
|  | { | 
|  | return reg32_poll_timeout(&base->MSTATUS, mask, mask, timeout_us); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Clear the MSTATUS bits and wait for them to be cleared. | 
|  | * | 
|  | * This spins forever for the bits to be cleared; | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be cleared. | 
|  | */ | 
|  | static inline void mcux_i3c_status_clear(I3C_Type *base, uint32_t mask) | 
|  | { | 
|  | /* Try to clear bit until it is cleared */ | 
|  | while (1) { | 
|  | base->MSTATUS = mask; | 
|  |  | 
|  | if (!mcux_i3c_status_is_set(base, mask)) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | k_busy_wait(1); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Clear transfer and IBI related bits in MSTATUS. | 
|  | * | 
|  | * This spins forever for those bits to be cleared; | 
|  | * | 
|  | * @see I3C_MSTATUS_MCTRLDONE_MASK | 
|  | * @see I3C_MSTATUS_COMPLETE_MASK | 
|  | * @see I3C_MSTATUS_IBIWON_MASK | 
|  | * @see I3C_MSTATUS_ERRWARN_MASK | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_status_clear_all(I3C_Type *base) | 
|  | { | 
|  | uint32_t mask = I3C_MSTATUS_MCTRLDONE_MASK | | 
|  | I3C_MSTATUS_COMPLETE_MASK | | 
|  | I3C_MSTATUS_IBIWON_MASK | | 
|  | I3C_MSTATUS_ERRWARN_MASK; | 
|  |  | 
|  | mcux_i3c_status_clear(base, mask); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Clear the MSTATUS bits and wait for them to be cleared with time out. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be cleared. | 
|  | * @param timeout_us Timeout in microsecond before bailing out. | 
|  | * | 
|  | * @retval 0 If bits are cleared before time out. | 
|  | * @retval -ETIMEDOUT | 
|  | */ | 
|  | static inline int mcux_i3c_status_clear_timeout(I3C_Type *base, uint32_t mask, | 
|  | uint32_t timeout_us) | 
|  | { | 
|  | bool result; | 
|  |  | 
|  | base->MSTATUS = mask; | 
|  | /* | 
|  | * Status should clear quickly so no extra delays between | 
|  | * checks. Use the delay_stmt to retry clearing the | 
|  | * status by writing to the MSTATUS register. | 
|  | */ | 
|  | result = WAIT_FOR(!mcux_i3c_status_is_set(base, mask), timeout_us, base->MSTATUS = mask); | 
|  | if (!result) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Spin wait for MSTATUS bit to be set, and clear it afterwards. | 
|  | * | 
|  | * Note that this spins forever waiting for bits to be set, and | 
|  | * to be cleared. | 
|  | * | 
|  | * @see mcux_i3c_status_wait | 
|  | * @see mcux_i3c_status_clear | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be set and to be cleared; | 
|  | */ | 
|  | static inline void mcux_i3c_status_wait_clear(I3C_Type *base, uint32_t mask) | 
|  | { | 
|  | mcux_i3c_status_wait(base, mask); | 
|  | mcux_i3c_status_clear(base, mask); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Wait for MSTATUS bit to be set, and clear it afterwards, with time out. | 
|  | * | 
|  | * @see mcux_i3c_status_wait_timeout | 
|  | * @see mcux_i3c_status_clear_timeout | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param mask Bits to be set and to be cleared. | 
|  | * @param timeout_us Timeout in microsecond before bailing out. | 
|  | * | 
|  | * @retval 0 If masked register value matches before time out. | 
|  | * @retval -ETIMEDOUT Timedout without matching. | 
|  | */ | 
|  | static inline int mcux_i3c_status_wait_clear_timeout(I3C_Type *base, uint32_t mask, | 
|  | uint32_t timeout_us) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = mcux_i3c_status_wait_timeout(base, mask, timeout_us); | 
|  | if (ret != 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = mcux_i3c_status_clear_timeout(base, mask, timeout_us); | 
|  |  | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Clear the MERRWARN register. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_errwarn_clear_all_nowait(I3C_Type *base) | 
|  | { | 
|  | base->MERRWARN = base->MERRWARN; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to start DAA process. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_request_daa(I3C_Type *base) | 
|  | { | 
|  | reg32_update(&base->MCTRL, | 
|  | I3C_MCTRL_REQUEST_MASK | I3C_MCTRL_IBIRESP_MASK | I3C_MCTRL_RDTERM_MASK, | 
|  | I3C_MCTRL_REQUEST_PROCESS_DAA | I3C_MCTRL_IBIRESP_NACK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to start auto IBI. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_request_auto_ibi(I3C_Type *base) | 
|  | { | 
|  | reg32_update(&base->MCTRL, | 
|  | I3C_MCTRL_REQUEST_MASK | I3C_MCTRL_IBIRESP_MASK | I3C_MCTRL_RDTERM_MASK, | 
|  | I3C_MCTRL_REQUEST_AUTO_IBI | I3C_MCTRL_IBIRESP_ACK_AUTO); | 
|  |  | 
|  | /* AUTO_IBI should result in IBIWON bit being set in status */ | 
|  | mcux_i3c_status_wait_clear(base, I3C_MSTATUS_IBIWON_MASK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Get the controller state. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * | 
|  | * @retval I3C_MSTATUS_STATE_IDLE | 
|  | * @retval I3C_MSTATUS_STATE_SLVREQ | 
|  | * @retval I3C_MSTATUS_STATE_MSGSDR | 
|  | * @retval I3C_MSTATUS_STATE_NORMACT | 
|  | * @retval I3C_MSTATUS_STATE_MSGDDR | 
|  | * @retval I3C_MSTATUS_STATE_DAA | 
|  | * @retval I3C_MSTATUS_STATE_IBIACK | 
|  | * @retval I3C_MSTATUS_STATE_IBIRCV | 
|  | */ | 
|  | static inline uint32_t mcux_i3c_state_get(I3C_Type *base) | 
|  | { | 
|  | uint32_t mstatus = base->MSTATUS; | 
|  | uint32_t state; | 
|  |  | 
|  | /* Make sure we are in a state where we can emit STOP */ | 
|  | state = (mstatus & I3C_MSTATUS_STATE_MASK) >> I3C_MSTATUS_STATE_SHIFT; | 
|  |  | 
|  | return state; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Wait for MSTATUS state | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param state MSTATUS state to wait for. | 
|  | * @param step_delay_us Delay in microsecond between each read of register | 
|  | *                      (cannot be 0). | 
|  | * @param total_delay_us Total delay in microsecond before bailing out. | 
|  | * | 
|  | * @retval 0 If masked register value matches before time out. | 
|  | * @retval -ETIMEDOUT Exhausted all delays without matching. | 
|  | */ | 
|  | static inline int mcux_i3c_state_wait_timeout(I3C_Type *base, uint32_t state, | 
|  | uint32_t step_delay_us, | 
|  | uint32_t total_delay_us) | 
|  | { | 
|  | uint32_t delayed = 0; | 
|  | int ret = -ETIMEDOUT; | 
|  |  | 
|  | while (delayed <= total_delay_us) { | 
|  | if (mcux_i3c_state_get(base) == state) { | 
|  | ret = 0; | 
|  | break; | 
|  | } | 
|  |  | 
|  | k_busy_wait(step_delay_us); | 
|  | delayed += step_delay_us; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Wait for MSTATUS to be IDLE | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_wait_idle(struct mcux_i3c_data *dev_data, I3C_Type *base) | 
|  | { | 
|  | while (mcux_i3c_state_get(base) != I3C_MSTATUS_STATE_IDLE) { | 
|  | k_condvar_wait(&dev_data->condvar, | 
|  | &dev_data->lock, | 
|  | K_FOREVER); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to emit START. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param addr Target address. | 
|  | * @param is_i2c True if this is I2C transactions, false if I3C. | 
|  | * @param is_read True if this is a read transaction, false if write. | 
|  | * @param read_sz Number of bytes to read if @p is_read is true. | 
|  | * | 
|  | * @return 0 if successful, or negative if error. | 
|  | */ | 
|  | static int mcux_i3c_request_emit_start(I3C_Type *base, uint8_t addr, bool is_i2c, | 
|  | bool is_read, size_t read_sz) | 
|  | { | 
|  | uint32_t mctrl; | 
|  | int ret = 0; | 
|  |  | 
|  | mctrl = is_i2c ? I3C_MCTRL_TYPE_I2C : I3C_MCTRL_TYPE_I3C; | 
|  | mctrl |= I3C_MCTRL_IBIRESP_NACK; | 
|  |  | 
|  | if (is_read) { | 
|  | mctrl |= I3C_MCTRL_DIR_READ; | 
|  |  | 
|  | /* How many bytes to read */ | 
|  | mctrl |= I3C_MCTRL_RDTERM(read_sz); | 
|  | } else { | 
|  | mctrl |= I3C_MCTRL_DIR_WRITE; | 
|  | } | 
|  |  | 
|  | mctrl |= I3C_MCTRL_REQUEST_EMIT_START_ADDR | I3C_MCTRL_ADDR(addr); | 
|  |  | 
|  | base->MCTRL = mctrl; | 
|  |  | 
|  | /* Wait for controller to say the operation is done */ | 
|  | ret = mcux_i3c_status_wait_clear_timeout(base, I3C_MSTATUS_MCTRLDONE_MASK, | 
|  | 1000); | 
|  | if (ret == 0) { | 
|  | /* Check for NACK */ | 
|  | if (mcux_i3c_has_error() & I3C_MERRWARN_NACK_MASK) { | 
|  | ret = -ENODEV; | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to emit STOP. | 
|  | * | 
|  | * This emits STOP and waits for controller to get out of NORMACT, | 
|  | * checking for errors. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param wait_stop True if need to wait for controller to be | 
|  | *                  no longer in NORMACT. | 
|  | */ | 
|  | static inline int mcux_i3c_do_request_emit_stop(I3C_Type *base, bool wait_stop) | 
|  | { | 
|  | uint32_t merrwarn; | 
|  |  | 
|  | reg32_update(&base->MCTRL, | 
|  | I3C_MCTRL_REQUEST_MASK | I3C_MCTRL_DIR_MASK | I3C_MCTRL_RDTERM_MASK, | 
|  | I3C_MCTRL_REQUEST_EMIT_STOP); | 
|  |  | 
|  | /* | 
|  | * EMIT_STOP request doesn't result in MCTRLDONE being cleared | 
|  | * so don't wait for it. | 
|  | */ | 
|  |  | 
|  | if (wait_stop) { | 
|  | /* | 
|  | * Note that we don't exactly wait for I3C_MSTATUS_STATE_IDLE. | 
|  | * If there is an incoming IBI, it will get stuck forever | 
|  | * as state would be I3C_MSTATUS_STATE_SLVREQ. | 
|  | */ | 
|  | while (reg32_test_match(&base->MSTATUS, I3C_MSTATUS_STATE_MASK, | 
|  | I3C_MSTATUS_STATE_NORMACT)) { | 
|  | merrwarn = mcux_i3c_has_error(); | 
|  | if (merrwarn) { | 
|  | /* | 
|  | * A timeout error has been observed on | 
|  | * an EMIT_STOP request. Refman doesn't say | 
|  | * how that could occur but clear it | 
|  | * and return the error. | 
|  | */ | 
|  | if (merrwarn & I3C_MERRWARN_TIMEOUT_MASK) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | return -EIO; | 
|  | } | 
|  | k_busy_wait(10); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to emit STOP. | 
|  | * | 
|  | * This emits STOP when controller is in NORMACT state as this is | 
|  | * the only valid state where STOP can be emitted. This also waits | 
|  | * for the controller to get out of NORMACT before returning and | 
|  | * retries if any timeout errors occur during the emit STOP. | 
|  | * | 
|  | * @param dev_data Pointer to device driver data | 
|  | * @param base Pointer to controller registers. | 
|  | * @param wait_stop True if need to wait for controller to be | 
|  | *                  no longer in NORMACT. | 
|  | */ | 
|  | static inline void mcux_i3c_request_emit_stop(struct mcux_i3c_data *dev_data, | 
|  | I3C_Type *base, bool wait_stop) | 
|  | { | 
|  | size_t retries; | 
|  |  | 
|  | /* | 
|  | * Stop is usually the last part of a transfer. | 
|  | * Sometimes, an error occurred before. We want to clear | 
|  | * it so any error as a result of emitting the stop | 
|  | * itself doesn't get incorrectly mixed together. | 
|  | */ | 
|  | if (mcux_i3c_has_error()) { | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | } | 
|  |  | 
|  | /* Make sure we are in a state where we can emit STOP */ | 
|  | if (!reg32_test_match(&base->MSTATUS, I3C_MSTATUS_STATE_MASK, | 
|  | I3C_MSTATUS_STATE_NORMACT)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | retries = 0; | 
|  | while (1) { | 
|  | int err = mcux_i3c_do_request_emit_stop(base, wait_stop); | 
|  |  | 
|  | if (err) { | 
|  | if ((err == -ETIMEDOUT) && (++retries <= I3C_MAX_STOP_RETRIES)) { | 
|  | LOG_WRN("Timeout on emit stop, retrying"); | 
|  | continue; | 
|  | } | 
|  | LOG_ERR("Error waiting for stop"); | 
|  | return; | 
|  | } | 
|  | /* | 
|  | * Success. If wait_stop was true, state should now | 
|  | * be IDLE or possibly SLVREQ. | 
|  | */ | 
|  | if (retries) { | 
|  | LOG_WRN("EMIT_STOP succeeded on %u retries", retries); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Release any threads that might have been blocked waiting for IDLE */ | 
|  | k_condvar_broadcast(&dev_data->condvar); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to NACK the incoming IBI. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_ibi_respond_nack(I3C_Type *base) | 
|  | { | 
|  | reg32_update(&base->MCTRL, | 
|  | I3C_MCTRL_REQUEST_MASK | I3C_MCTRL_IBIRESP_MASK, | 
|  | I3C_MCTRL_REQUEST_IBI_ACK_NACK | I3C_MCTRL_IBIRESP_NACK); | 
|  |  | 
|  | mcux_i3c_status_wait_clear(base, I3C_MSTATUS_MCTRLDONE_MASK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to ACK the incoming IBI. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_ibi_respond_ack(I3C_Type *base) | 
|  | { | 
|  | reg32_update(&base->MCTRL, | 
|  | I3C_MCTRL_REQUEST_MASK | I3C_MCTRL_IBIRESP_MASK, | 
|  | I3C_MCTRL_REQUEST_IBI_ACK_NACK | I3C_MCTRL_IBIRESP_ACK); | 
|  |  | 
|  | mcux_i3c_status_wait_clear(base, I3C_MSTATUS_MCTRLDONE_MASK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Get the number of bytes in RX FIFO. | 
|  | * | 
|  | * This returns the number of bytes in RX FIFO which | 
|  | * can be read. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * | 
|  | * @return Number of bytes in RX FIFO. | 
|  | */ | 
|  | static inline int mcux_i3c_fifo_rx_count_get(I3C_Type *base) | 
|  | { | 
|  | uint32_t mdatactrl = base->MDATACTRL; | 
|  |  | 
|  | return (int)((mdatactrl & I3C_MDATACTRL_RXCOUNT_MASK) >> I3C_MDATACTRL_RXCOUNT_SHIFT); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Tell controller to flush both TX and RX FIFOs. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_fifo_flush(I3C_Type *base) | 
|  | { | 
|  | base->MDATACTRL = I3C_MDATACTRL_FLUSHFB_MASK | I3C_MDATACTRL_FLUSHTB_MASK; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Prepare the controller for transfers. | 
|  | * | 
|  | * This is simply a wrapper to clear out status bits, | 
|  | * and error bits. Also this tells the controller to | 
|  | * flush both TX and RX FIFOs. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | */ | 
|  | static inline void mcux_i3c_xfer_reset(I3C_Type *base) | 
|  | { | 
|  | mcux_i3c_status_clear_all(base); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | mcux_i3c_fifo_flush(base); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Drain RX FIFO. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | */ | 
|  | static void mcux_i3c_fifo_rx_drain(const struct device *dev) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | I3C_Type *base = config->base; | 
|  | uint8_t buf; | 
|  |  | 
|  | /* Read from FIFO as long as RXPEND is set. */ | 
|  | while (mcux_i3c_status_is_set(base, I3C_MSTATUS_RXPEND_MASK)) { | 
|  | buf = base->MRDATAB; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Find a registered I3C target device. | 
|  | * | 
|  | * This returns the I3C device descriptor of the I3C device | 
|  | * matching the incoming @p id. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | * @param id Pointer to I3C device ID. | 
|  | * | 
|  | * @return @see i3c_device_find. | 
|  | */ | 
|  | static | 
|  | struct i3c_device_desc *mcux_i3c_device_find(const struct device *dev, | 
|  | const struct i3c_device_id *id) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  |  | 
|  | return i3c_dev_list_find(&config->common.dev_list, id); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Perform bus recovery. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | */ | 
|  | static int mcux_i3c_recover_bus(const struct device *dev) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | I3C_Type *base = config->base; | 
|  | int ret = 0; | 
|  |  | 
|  | /* | 
|  | * 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 (mcux_i3c_state_get(base) == I3C_MSTATUS_STATE_NORMACT) { | 
|  | mcux_i3c_request_emit_stop(dev->data, base, true); | 
|  | }; | 
|  |  | 
|  | /* Exhaust all target initiated IBI */ | 
|  | while (mcux_i3c_status_is_set(base, I3C_MSTATUS_SLVSTART_MASK)) { | 
|  | /* Tell the controller to perform auto IBI. */ | 
|  | mcux_i3c_request_auto_ibi(base); | 
|  |  | 
|  | if (mcux_i3c_status_wait_clear_timeout(base, I3C_MSTATUS_COMPLETE_MASK, | 
|  | 1000) == -ETIMEDOUT) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Once auto IBI is done, discard bytes in FIFO. */ | 
|  | mcux_i3c_fifo_rx_drain(dev); | 
|  |  | 
|  | /* | 
|  | * There might be other IBIs waiting. | 
|  | * So pause a bit to let other targets initiates | 
|  | * their IBIs. | 
|  | */ | 
|  | k_busy_wait(100); | 
|  | } | 
|  |  | 
|  | if (reg32_poll_timeout(&base->MSTATUS, I3C_MSTATUS_STATE_MASK, | 
|  | I3C_MSTATUS_STATE_IDLE, 1000) == -ETIMEDOUT) { | 
|  | ret = -EBUSY; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Perform one read transaction. | 
|  | * | 
|  | * This reads from RX FIFO until COMPLETE bit is set in MSTATUS | 
|  | * or time out. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param data Pointer to controller device instance data. | 
|  | * @param buf Buffer to store data. | 
|  | * @param buf_sz Buffer size in bytes. | 
|  | * | 
|  | * @return Number of bytes read, or negative if error. | 
|  | */ | 
|  | static int mcux_i3c_do_one_xfer_read(I3C_Type *base, struct mcux_i3c_data *data, | 
|  | uint8_t *buf, uint8_t buf_sz, bool ibi) | 
|  | { | 
|  | int ret = 0; | 
|  | int offset = 0; | 
|  |  | 
|  | while (offset < buf_sz) { | 
|  | /* | 
|  | * Transfer data from FIFO into buffer. Read | 
|  | * in a loop until data is unavailable in the FIFO. | 
|  | */ | 
|  | while (offset < buf_sz) { | 
|  | if (mcux_i3c_fifo_rx_count_get(base) == 0) { | 
|  | /* Enable Receive pending interrupt */ | 
|  | base->MINTSET = I3C_MSTATUS_RXPEND_MASK; | 
|  |  | 
|  | /* Wait for data to arrive or an error */ | 
|  | if (k_sem_take(&data->device_sync_sem, I3C_TRANSFER_TIMEOUT_MSEC)) { | 
|  | ret = -ETIMEDOUT; | 
|  | } | 
|  | /* We break out of the loop to see if the interrupt | 
|  | * was due to an error. | 
|  | */ | 
|  | break; | 
|  | } else { | 
|  | buf[offset++] = (uint8_t)base->MRDATAB; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * If timed out, we abort the transaction. | 
|  | */ | 
|  | if ((mcux_i3c_has_error() & I3C_MERRWARN_TIMEOUT_MASK) || ret) { | 
|  | ret = -ETIMEDOUT; | 
|  |  | 
|  | /* for ibi, ignore timeout err if any bytes were | 
|  | * read, since the code doesn't know how many | 
|  | * bytes will be sent by device. | 
|  | */ | 
|  | if (ibi && offset) { | 
|  | ret = offset; | 
|  | } else { | 
|  | LOG_ERR("Timeout error"); | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | /* If no errors, then return the number of bytes read */ | 
|  | if (ret > 0) { | 
|  | ret = offset; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Perform one write transaction. | 
|  | * | 
|  | * This writes all data in @p buf to TX FIFO or time out | 
|  | * waiting for FIFO spaces. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param data Pointer to controller device instance data. | 
|  | * @param buf Buffer containing data to be sent. | 
|  | * @param buf_sz Number of bytes in @p buf to send. | 
|  | * @param no_ending True if not to signal end of write message. | 
|  | * | 
|  | * @return Number of bytes written, or negative if error. | 
|  | */ | 
|  | static int mcux_i3c_do_one_xfer_write(I3C_Type *base, struct mcux_i3c_data *data, | 
|  | uint8_t *buf, uint8_t buf_sz, bool no_ending) | 
|  | { | 
|  | int offset = 0; | 
|  | int remaining = buf_sz; | 
|  | int ret = 0; | 
|  |  | 
|  | while (remaining > 0) { | 
|  | if (base->MDATACTRL & I3C_MDATACTRL_TXFULL_MASK) { | 
|  | /* Enable TX buffer ready interrupt */ | 
|  | base->MINTSET = I3C_MSTATUS_TXNOTFULL_MASK; | 
|  |  | 
|  | /* Wait for the transfer to complete */ | 
|  | ret = k_sem_take(&data->device_sync_sem, I3C_TRANSFER_TIMEOUT_MSEC); | 
|  | if (ret) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((remaining > 1) || no_ending) { | 
|  | base->MWDATAB = (uint32_t)buf[offset]; | 
|  | } else { | 
|  | base->MWDATABE = (uint32_t)buf[offset]; | 
|  | } | 
|  |  | 
|  | offset += 1; | 
|  | remaining -= 1; | 
|  | } | 
|  |  | 
|  | if (!ret) { | 
|  | /* Return the number of bytes received */ | 
|  | ret = offset; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Perform one transfer transaction. | 
|  | * | 
|  | * @param base Pointer to controller registers. | 
|  | * @param data Pointer to controller device instance data. | 
|  | * @param addr Target address. | 
|  | * @param is_i2c True if this is I2C transactions, false if I3C. | 
|  | * @param buf Buffer for data to be sent or received. | 
|  | * @param buf_sz Buffer size in bytes. | 
|  | * @param is_read True if this is a read transaction, false if write. | 
|  | * @param emit_start True if START is needed before read/write. | 
|  | * @param emit_stop True if STOP is needed after read/write. | 
|  | * @param no_ending True if not to signal end of write message. | 
|  | * | 
|  | * @return Number of bytes read/written, or negative if error. | 
|  | */ | 
|  | static int mcux_i3c_do_one_xfer(I3C_Type *base, struct mcux_i3c_data *data, | 
|  | uint8_t addr, bool is_i2c, | 
|  | uint8_t *buf, size_t buf_sz, | 
|  | bool is_read, bool emit_start, bool emit_stop, | 
|  | bool no_ending) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | mcux_i3c_status_clear_all(base); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  |  | 
|  | /* Emit START if so desired */ | 
|  | if (emit_start) { | 
|  | ret = mcux_i3c_request_emit_start(base, addr, is_i2c, is_read, buf_sz); | 
|  | if (ret != 0) { | 
|  | emit_stop = true; | 
|  |  | 
|  | goto out_one_xfer; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((buf == NULL) || (buf_sz == 0)) { | 
|  | goto out_one_xfer; | 
|  | } | 
|  |  | 
|  | if (is_read) { | 
|  | ret = mcux_i3c_do_one_xfer_read(base, data, buf, buf_sz, false); | 
|  | } else { | 
|  | ret = mcux_i3c_do_one_xfer_write(base, data, buf, buf_sz, no_ending); | 
|  | } | 
|  |  | 
|  | if (ret < 0) { | 
|  | goto out_one_xfer; | 
|  | } | 
|  |  | 
|  | if (is_read || !no_ending) { | 
|  | /* | 
|  | * Wait for controller to say the operation is done. | 
|  | * Save time by not clearing the bit. | 
|  | */ | 
|  | ret = mcux_i3c_status_wait_timeout(base, I3C_MSTATUS_COMPLETE_MASK, 1000); | 
|  | if (ret != 0) { | 
|  | LOG_DBG("%s: timed out addr 0x%02x, buf_sz %u", | 
|  | __func__, addr, buf_sz); | 
|  | emit_stop = true; | 
|  |  | 
|  | goto out_one_xfer; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mcux_i3c_has_error()) { | 
|  | ret = -EIO; | 
|  | } | 
|  |  | 
|  | out_one_xfer: | 
|  | if (emit_stop) { | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Transfer messages in I3C mode. | 
|  | * | 
|  | * @see i3c_transfer | 
|  | * | 
|  | * @param dev Pointer to device driver instance. | 
|  | * @param target Pointer to target device descriptor. | 
|  | * @param msgs Pointer to I3C messages. | 
|  | * @param num_msgs Number of messages to transfers. | 
|  | * | 
|  | * @return @see i3c_transfer | 
|  | */ | 
|  | static int mcux_i3c_transfer(const struct device *dev, | 
|  | struct i3c_device_desc *target, | 
|  | struct i3c_msg *msgs, | 
|  | uint8_t num_msgs) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *dev_data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | int ret; | 
|  | bool send_broadcast = true; | 
|  |  | 
|  | if (target->dynamic_addr == 0U) { | 
|  | ret = -EINVAL; | 
|  | goto out_xfer_i3c; | 
|  | } | 
|  |  | 
|  | k_mutex_lock(&dev_data->lock, K_FOREVER); | 
|  |  | 
|  | mcux_i3c_wait_idle(dev_data, base); | 
|  |  | 
|  | mcux_i3c_xfer_reset(base); | 
|  |  | 
|  | /* Iterate over all the messages */ | 
|  | for (int i = 0; i < num_msgs; i++) { | 
|  | 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. | 
|  | */ | 
|  | bool emit_start = (i == 0) || | 
|  | ((msgs[i].flags & I3C_MSG_RESTART) == I3C_MSG_RESTART); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | if (next_is_write && !next_is_restart) { | 
|  | no_ending = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Send broadcast header on first transfer or after a STOP, | 
|  | * unless flag is set not to. | 
|  | */ | 
|  | if (!(msgs[i].flags & I3C_MSG_NBCH) && (send_broadcast)) { | 
|  | while (1) { | 
|  | ret = mcux_i3c_request_emit_start(base, I3C_BROADCAST_ADDR, | 
|  | false, false, 0); | 
|  | if (ret == -ENODEV) { | 
|  | LOG_WRN("emit start of broadcast addr got NACK, maybe IBI"); | 
|  | /* wait for idle then try again */ | 
|  | mcux_i3c_wait_idle(dev_data, base); | 
|  | continue; | 
|  | } | 
|  | if (ret < 0) { | 
|  | LOG_ERR("emit start of broadcast addr failed, error (%d)", | 
|  | ret); | 
|  | goto out_xfer_i3c_stop_unlock; | 
|  | } | 
|  | break; | 
|  | } | 
|  | send_broadcast = false; | 
|  | } | 
|  |  | 
|  | ret = mcux_i3c_do_one_xfer(base, dev_data, target->dynamic_addr, false, | 
|  | msgs[i].buf, msgs[i].len, | 
|  | is_read, emit_start, emit_stop, no_ending); | 
|  | if (ret < 0) { | 
|  | goto out_xfer_i3c_stop_unlock; | 
|  | } | 
|  |  | 
|  | /* write back the total number of bytes transferred */ | 
|  | msgs[i].num_xfer = ret; | 
|  |  | 
|  | if (emit_stop) { | 
|  | /* After a STOP, send broadcast header before next msg */ | 
|  | send_broadcast = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = 0; | 
|  |  | 
|  | out_xfer_i3c_stop_unlock: | 
|  | mcux_i3c_request_emit_stop(dev_data, base, true); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | mcux_i3c_status_clear_all(base); | 
|  | k_mutex_unlock(&dev_data->lock); | 
|  |  | 
|  | out_xfer_i3c: | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Perform Dynamic Address Assignment. | 
|  | * | 
|  | * @see i3c_do_daa | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | * | 
|  | * @return @see i3c_do_daa | 
|  | */ | 
|  | static int mcux_i3c_do_daa(const struct device *dev) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | int ret = 0; | 
|  | uint8_t rx_buf[8] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; | 
|  | size_t rx_count; | 
|  | uint8_t rx_size = 0; | 
|  | uint32_t intmask; | 
|  |  | 
|  | k_mutex_lock(&data->lock, K_FOREVER); | 
|  |  | 
|  | ret = mcux_i3c_state_wait_timeout(base, I3C_MSTATUS_STATE_IDLE, 100, 100000); | 
|  | if (ret == -ETIMEDOUT) { | 
|  | goto out_daa_unlock; | 
|  | } | 
|  |  | 
|  | LOG_DBG("DAA: ENTDAA"); | 
|  |  | 
|  | /* Disable I3C IRQ sources while we configure stuff. */ | 
|  | intmask = mcux_i3c_interrupt_disable(base); | 
|  |  | 
|  | mcux_i3c_xfer_reset(base); | 
|  |  | 
|  | /* Emit process DAA */ | 
|  | mcux_i3c_request_daa(base); | 
|  |  | 
|  | /* Loop until no more responses from devices */ | 
|  | do { | 
|  | /* Loop to grab data from devices (Provisioned ID, BCR and DCR) */ | 
|  | do { | 
|  | if (mcux_i3c_has_error()) { | 
|  | LOG_ERR("DAA recv error"); | 
|  |  | 
|  | ret = -EIO; | 
|  |  | 
|  | goto out_daa; | 
|  | } | 
|  |  | 
|  | rx_count = mcux_i3c_fifo_rx_count_get(base); | 
|  | while (mcux_i3c_status_is_set(base, I3C_MSTATUS_RXPEND_MASK) && | 
|  | (rx_count != 0U)) { | 
|  | rx_buf[rx_size] = (uint8_t)(base->MRDATAB & | 
|  | I3C_MRDATAB_VALUE_MASK); | 
|  | rx_size++; | 
|  | rx_count--; | 
|  | } | 
|  | } while (!mcux_i3c_status_is_set(base, I3C_MSTATUS_MCTRLDONE_MASK)); | 
|  |  | 
|  | mcux_i3c_status_clear(base, I3C_MSTATUS_MCTRLDONE_MASK); | 
|  |  | 
|  | /* Figure out what address to assign to device */ | 
|  | if ((mcux_i3c_state_get(base) == I3C_MSTATUS_STATE_DAA) && | 
|  | (mcux_i3c_status_is_set(base, I3C_MSTATUS_BETWEEN_MASK))) { | 
|  | struct i3c_device_desc *target; | 
|  | uint16_t vendor_id; | 
|  | uint32_t part_no; | 
|  | uint64_t pid; | 
|  | uint8_t dyn_addr; | 
|  |  | 
|  | rx_size = 0; | 
|  |  | 
|  | /* Vendor ID portion of Provisioned ID */ | 
|  | vendor_id = (((uint16_t)rx_buf[0] << 8U) | (uint16_t)rx_buf[1]) & | 
|  | 0xFFFEU; | 
|  |  | 
|  | /* Part Number portion of Provisioned ID */ | 
|  | 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]; | 
|  |  | 
|  | /* ... and 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); | 
|  |  | 
|  | 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) { | 
|  | goto out_daa; | 
|  | } | 
|  |  | 
|  | /* 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->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 */ | 
|  | base->MWDATAB = dyn_addr; | 
|  | mcux_i3c_request_daa(base); | 
|  |  | 
|  | LOG_DBG("PID 0x%04x%08x assigned dynamic address 0x%02x", | 
|  | vendor_id, part_no, dyn_addr); | 
|  | } | 
|  |  | 
|  | } while (!mcux_i3c_status_is_set(base, I3C_MSTATUS_COMPLETE_MASK)); | 
|  |  | 
|  | out_daa: | 
|  | /* Clear all flags. */ | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | mcux_i3c_status_clear_all(base); | 
|  |  | 
|  | /* Re-Enable I3C IRQ sources. */ | 
|  | mcux_i3c_interrupt_enable(base, intmask); | 
|  |  | 
|  | out_daa_unlock: | 
|  | k_mutex_unlock(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Send Common Command Code (CCC). | 
|  | * | 
|  | * @see i3c_do_ccc | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | * @param payload Pointer to CCC payload. | 
|  | * | 
|  | * @return @see i3c_do_ccc | 
|  | */ | 
|  | static int mcux_i3c_do_ccc(const struct device *dev, | 
|  | struct i3c_ccc_payload *payload) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | int ret = 0; | 
|  |  | 
|  | if (payload == NULL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (config->common.dev_list.num_i3c == 0) { | 
|  | /* | 
|  | * No i3c devices in dev tree. Just return so | 
|  | * we don't get errors doing cmds when there | 
|  | * are no devices listening/responding. | 
|  | */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | k_mutex_lock(&data->lock, K_FOREVER); | 
|  |  | 
|  | mcux_i3c_xfer_reset(base); | 
|  |  | 
|  | LOG_DBG("CCC[0x%02x]", payload->ccc.id); | 
|  |  | 
|  | /* Emit START */ | 
|  | ret = mcux_i3c_request_emit_start(base, I3C_BROADCAST_ADDR, false, 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_ccc_stop; | 
|  | } | 
|  |  | 
|  | /* Write the CCC code */ | 
|  | mcux_i3c_status_clear_all(base); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | ret = mcux_i3c_do_one_xfer_write(base, data, &payload->ccc.id, 1, | 
|  | payload->ccc.data_len > 0); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("CCC[0x%02x] %s command error (%d)", | 
|  | payload->ccc.id, | 
|  | i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", | 
|  | ret); | 
|  |  | 
|  | goto out_ccc_stop; | 
|  | } | 
|  |  | 
|  | /* Write additional data for CCC if needed */ | 
|  | if (payload->ccc.data_len > 0) { | 
|  | mcux_i3c_status_clear_all(base); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | ret = mcux_i3c_do_one_xfer_write(base, data, payload->ccc.data, | 
|  | payload->ccc.data_len, false); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("CCC[0x%02x] %s command payload error (%d)", | 
|  | payload->ccc.id, | 
|  | i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", | 
|  | ret); | 
|  |  | 
|  | goto out_ccc_stop; | 
|  | } | 
|  |  | 
|  | /* write back the total number of bytes transferred */ | 
|  | payload->ccc.num_xfer = ret; | 
|  | } | 
|  |  | 
|  | /* Wait for controller to say the operation is done */ | 
|  | ret = mcux_i3c_status_wait_clear_timeout(base, I3C_MSTATUS_COMPLETE_MASK, 1000); | 
|  | if (ret != 0) { | 
|  | goto out_ccc_stop; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | bool emit_start = idx == 0; | 
|  |  | 
|  | ret = mcux_i3c_do_one_xfer(base, data, | 
|  | tgt_payload->addr, false, | 
|  | tgt_payload->data, | 
|  | tgt_payload->data_len, | 
|  | is_read, emit_start, false, false); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("CCC[0x%02x] target payload error (%d)", | 
|  | payload->ccc.id, ret); | 
|  |  | 
|  | goto out_ccc_stop; | 
|  | } | 
|  |  | 
|  | /* write back the total number of bytes transferred */ | 
|  | tgt_payload->num_xfer = ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | out_ccc_stop: | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  |  | 
|  | if (ret > 0) { | 
|  | ret = 0; | 
|  | } | 
|  |  | 
|  | k_mutex_unlock(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_I3C_USE_IBI | 
|  | /** | 
|  | * @brief Callback to service target initiated IBIs. | 
|  | * | 
|  | * @param work Pointer to k_work item. | 
|  | */ | 
|  | static void mcux_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 mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | struct i3c_device_desc *target = NULL; | 
|  | uint32_t mstatus, ibitype, ibiaddr; | 
|  | int ret; | 
|  |  | 
|  | k_mutex_lock(&data->lock, K_FOREVER); | 
|  |  | 
|  | if (mcux_i3c_state_get(base) != I3C_MSTATUS_STATE_SLVREQ) { | 
|  | LOG_DBG("IBI work %p running not because of IBI", work); | 
|  | LOG_DBG("MSTATUS 0x%08x MERRWARN 0x%08x", | 
|  | base->MSTATUS, base->MERRWARN); | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  |  | 
|  | goto out_ibi_work; | 
|  | }; | 
|  |  | 
|  | /* Use auto IBI to service the IBI */ | 
|  | mcux_i3c_request_auto_ibi(base); | 
|  |  | 
|  | mstatus = sys_read32((mem_addr_t)&base->MSTATUS); | 
|  | ibiaddr = (mstatus & I3C_MSTATUS_IBIADDR_MASK) >> I3C_MSTATUS_IBIADDR_SHIFT; | 
|  |  | 
|  | /* | 
|  | * Note that the I3C_MSTATUS_IBI_TYPE_* are not shifted right. | 
|  | * So no need to shift here. | 
|  | */ | 
|  | ibitype = (mstatus & I3C_MSTATUS_IBITYPE_MASK); | 
|  |  | 
|  | /* | 
|  | * Wait for COMPLETE bit to be set to indicate auto IBI | 
|  | * has finished for hot-join and controller role request. | 
|  | * For target interrupts, the IBI payload may be longer | 
|  | * than the RX FIFO so we won't get the COMPLETE bit set | 
|  | * at the first round of data read. So checking of | 
|  | * COMPLETE bit is deferred to the reading. | 
|  | */ | 
|  | switch (ibitype) { | 
|  | case I3C_MSTATUS_IBITYPE_HJ: | 
|  | __fallthrough; | 
|  |  | 
|  | case I3C_MSTATUS_IBITYPE_MR: | 
|  | if (mcux_i3c_status_wait_timeout(base, I3C_MSTATUS_COMPLETE_MASK, | 
|  | 1000) == -ETIMEDOUT) { | 
|  | LOG_ERR("Timeout waiting for COMPLETE"); | 
|  |  | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  |  | 
|  | goto out_ibi_work; | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | }; | 
|  |  | 
|  | switch (ibitype) { | 
|  | case I3C_MSTATUS_IBITYPE_IBI: | 
|  | target = i3c_dev_list_i3c_addr_find(dev, (uint8_t)ibiaddr); | 
|  | if (target != NULL) { | 
|  | ret = mcux_i3c_do_one_xfer_read(base, data, &payload[0], | 
|  | sizeof(payload), true); | 
|  | if (ret >= 0) { | 
|  | payload_sz = (size_t)ret; | 
|  | } else { | 
|  | LOG_ERR("Error reading IBI payload"); | 
|  |  | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  |  | 
|  | goto out_ibi_work; | 
|  | } | 
|  | } else { | 
|  | LOG_ERR("IBI from unknown device addr 0x%x", ibiaddr); | 
|  | /* NACK IBI coming from unknown device */ | 
|  | mcux_i3c_ibi_respond_nack(base); | 
|  | } | 
|  | break; | 
|  | case I3C_MSTATUS_IBITYPE_HJ: | 
|  | mcux_i3c_ibi_respond_ack(base); | 
|  | break; | 
|  | case I3C_MSTATUS_IBITYPE_MR: | 
|  | LOG_DBG("Controller role handoff not supported"); | 
|  | mcux_i3c_ibi_respond_nack(base); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (mcux_i3c_has_error()) { | 
|  | /* | 
|  | * If the controller detects any errors, simply | 
|  | * emit a STOP to abort the IBI. The target will | 
|  | * raise IBI again if so desired. | 
|  | */ | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  |  | 
|  | goto out_ibi_work; | 
|  | } | 
|  |  | 
|  | switch (ibitype) { | 
|  | case I3C_MSTATUS_IBITYPE_IBI: | 
|  | if (target != NULL) { | 
|  | if (i3c_ibi_work_enqueue_target_irq(target, | 
|  | &payload[0], payload_sz) != 0) { | 
|  | LOG_ERR("Error enqueue IBI IRQ work"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Finishing the IBI transaction */ | 
|  | mcux_i3c_request_emit_stop(data, base, true); | 
|  | break; | 
|  | case I3C_MSTATUS_IBITYPE_HJ: | 
|  | if (i3c_ibi_work_enqueue_hotjoin(dev) != 0) { | 
|  | LOG_ERR("Error enqueue IBI HJ work"); | 
|  | } | 
|  | break; | 
|  | case I3C_MSTATUS_IBITYPE_MR: | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | out_ibi_work: | 
|  | k_mutex_unlock(&data->lock); | 
|  |  | 
|  | /* Re-enable target initiated IBI interrupt. */ | 
|  | base->MINTSET = I3C_MINTSET_SLVSTART_MASK; | 
|  | } | 
|  |  | 
|  | static void mcux_i3c_ibi_rules_setup(struct mcux_i3c_data *data, | 
|  | I3C_Type *base) | 
|  | { | 
|  | 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] & I3C_MIBIRULES_ADDR0_MASK; | 
|  |  | 
|  | /* Shift into correct place */ | 
|  | addr_6bit <<= idx * I3C_MIBIRULES_ADDR1_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 |= I3C_MIBIRULES_MSB0_MASK; | 
|  | } | 
|  |  | 
|  | if (!data->ibi.has_mandatory_byte) { | 
|  | /* The NOBYTE field is 1 if there is no mandatory byte */ | 
|  | ibi_rules |= I3C_MIBIRULES_NOBYTE_MASK; | 
|  | } | 
|  |  | 
|  | /* Update the register */ | 
|  | base->MIBIRULES = ibi_rules; | 
|  |  | 
|  | LOG_DBG("MIBIRULES 0x%08x", ibi_rules); | 
|  | } | 
|  |  | 
|  | int mcux_i3c_ibi_enable(const struct device *dev, | 
|  | struct i3c_device_desc *target) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | struct i3c_ccc_events i3c_events; | 
|  | uint8_t idx; | 
|  | bool msb, has_mandatory_byte; | 
|  | int ret = 0; | 
|  |  | 
|  | if (!i3c_device_is_ibi_capable(target)) { | 
|  | ret = -EINVAL; | 
|  | goto out1; | 
|  | } | 
|  |  | 
|  | if (data->ibi.num_addr >= ARRAY_SIZE(data->ibi.addr)) { | 
|  | /* No more free entries in the IBI Rules table */ | 
|  | ret = -ENOMEM; | 
|  | goto out1; | 
|  | } | 
|  |  | 
|  | /* Check for duplicate */ | 
|  | for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { | 
|  | if (data->ibi.addr[idx] == target->dynamic_addr) { | 
|  | ret = -EINVAL; | 
|  | goto out1; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Disable controller interrupt while we configure IBI rules. */ | 
|  | base->MINTCLR = I3C_MINTCLR_SLVSTART_MASK; | 
|  |  | 
|  | LOG_DBG("IBI enabling for 0x%02x (BCR 0x%02x)", | 
|  | target->dynamic_addr, target->bcr); | 
|  |  | 
|  | msb = (target->dynamic_addr & BIT(6)) == BIT(6); | 
|  | 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. | 
|  | */ | 
|  | 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) { | 
|  | LOG_ERR("New IBI does not have same mandatory byte requirement" | 
|  | " as previous IBI"); | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  | if (msb != data->ibi.msb) { | 
|  | LOG_ERR("New IBI does not have same msb as previous IBI"); | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* 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)) { | 
|  | LOG_ERR("Cannot support more IBIs"); | 
|  | ret = -ENOTSUP; | 
|  | goto out; | 
|  | } | 
|  | } 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; | 
|  |  | 
|  | mcux_i3c_ibi_rules_setup(data, base); | 
|  |  | 
|  | /* Tell target to enable IBI */ | 
|  | 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: | 
|  | if (data->ibi.num_addr > 0U) { | 
|  | /* | 
|  | * Enable controller to raise interrupt when a target | 
|  | * initiates IBI. | 
|  | */ | 
|  | base->MINTSET = I3C_MINTSET_SLVSTART_MASK; | 
|  | } | 
|  | out1: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int mcux_i3c_ibi_disable(const struct device *dev, | 
|  | struct i3c_device_desc *target) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | struct i3c_ccc_events i3c_events; | 
|  | int ret = 0; | 
|  | int idx; | 
|  |  | 
|  | if (!i3c_device_is_ibi_capable(target)) { | 
|  | ret = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | 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)) { | 
|  | /* Target is not in list of registered addresses. */ | 
|  | ret = -ENODEV; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Disable controller interrupt while we configure IBI rules. */ | 
|  | base->MINTCLR = I3C_MINTCLR_SLVSTART_MASK; | 
|  |  | 
|  | data->ibi.addr[idx] = 0U; | 
|  | data->ibi.num_addr -= 1U; | 
|  |  | 
|  | /* Tell target to disable 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); | 
|  | } | 
|  |  | 
|  | mcux_i3c_ibi_rules_setup(data, base); | 
|  |  | 
|  | if (data->ibi.num_addr > 0U) { | 
|  | /* | 
|  | * Enable controller to raise interrupt when a target | 
|  | * initiates IBI. | 
|  | */ | 
|  | base->MINTSET = I3C_MINTSET_SLVSTART_MASK; | 
|  | } | 
|  | out: | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | #endif /* CONFIG_I3C_USE_IBI */ | 
|  |  | 
|  | /** | 
|  | * @brief Interrupt Service Routine | 
|  | * | 
|  | * Currently only services interrupts when any target initiates IBIs. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | */ | 
|  | static void mcux_i3c_isr(const struct device *dev) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | I3C_Type *base = config->base; | 
|  | struct mcux_i3c_data *dev_data = dev->data; | 
|  | uint32_t interrupt_enable = base->MINTSET; | 
|  |  | 
|  | #ifdef CONFIG_I3C_USE_IBI | 
|  | /* Target initiated IBIs */ | 
|  | if (mcux_i3c_status_is_set(base, I3C_MSTATUS_SLVSTART_MASK)) { | 
|  | int err; | 
|  |  | 
|  | /* Clear SLVSTART interrupt */ | 
|  | base->MSTATUS = I3C_MSTATUS_SLVSTART_MASK; | 
|  |  | 
|  | /* | 
|  | * Disable further target initiated IBI interrupt | 
|  | * while we try to service the current one. | 
|  | */ | 
|  | base->MINTCLR = I3C_MINTCLR_SLVSTART_MASK; | 
|  |  | 
|  | /* | 
|  | * Handle IBI in workqueue. | 
|  | */ | 
|  | err = i3c_ibi_work_enqueue_cb(dev, mcux_i3c_ibi_work); | 
|  | if (err) { | 
|  | LOG_ERR("Error enqueuing ibi work, err %d", err); | 
|  | base->MINTSET = I3C_MINTCLR_SLVSTART_MASK; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (interrupt_enable & I3C_MSTATUS_RXPEND_MASK) { | 
|  | /* Disable RX buffer ready interrupt */ | 
|  | base->MINTCLR = I3C_MSTATUS_RXPEND_MASK; | 
|  | k_sem_give(&dev_data->device_sync_sem); | 
|  | } else if (interrupt_enable & I3C_MSTATUS_TXNOTFULL_MASK) { | 
|  | /* Disable TX buffer ready interrupt */ | 
|  | base->MINTCLR = I3C_MSTATUS_TXNOTFULL_MASK; | 
|  | k_sem_give(&dev_data->device_sync_sem); | 
|  | } else { | 
|  | /* Nothing to do right now */ | 
|  | } | 
|  |  | 
|  | if (interrupt_enable & I3C_MSTATUS_ERRWARN_MASK) { | 
|  | merrwarn_reg = base->MERRWARN; | 
|  | base->MERRWARN = base->MERRWARN; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Configure I3C hardware. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | * @param type Type of configuration parameters being passed | 
|  | *             in @p config. | 
|  | * @param config Pointer to the configuration parameters. | 
|  | * | 
|  | * @retval 0 If successful. | 
|  | * @retval -EINVAL If invalid configure parameters. | 
|  | * @retval -EIO General Input/Output errors. | 
|  | * @retval -ENOSYS If not implemented. | 
|  | */ | 
|  | static int mcux_i3c_configure(const struct device *dev, | 
|  | enum i3c_config_type type, void *config) | 
|  | { | 
|  | const struct mcux_i3c_config *dev_cfg = dev->config; | 
|  | struct mcux_i3c_data *dev_data = dev->data; | 
|  | I3C_Type *base = dev_cfg->base; | 
|  | i3c_master_config_t master_config; | 
|  | struct i3c_config_controller *ctrl_cfg = config; | 
|  | uint32_t clock_freq; | 
|  | int ret = 0; | 
|  |  | 
|  | if (type != I3C_CONFIG_CONTROLLER) { | 
|  | ret = -EINVAL; | 
|  | goto out_configure; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check for valid configuration parameters. | 
|  | * | 
|  | * Currently, must be the primary controller. | 
|  | */ | 
|  | if ((ctrl_cfg->is_secondary) || | 
|  | (ctrl_cfg->scl.i2c == 0U) || | 
|  | (ctrl_cfg->scl.i3c == 0U)) { | 
|  | ret = -EINVAL; | 
|  | goto out_configure; | 
|  | } | 
|  |  | 
|  | /* Get the clock frequency */ | 
|  | if (clock_control_get_rate(dev_cfg->clock_dev, dev_cfg->clock_subsys, | 
|  | &clock_freq)) { | 
|  | ret = -EINVAL; | 
|  | goto out_configure; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Save requested config so next config_get() call returns the | 
|  | * correct values. | 
|  | */ | 
|  | (void)memcpy(&dev_data->common.ctrl_config, ctrl_cfg, sizeof(*ctrl_cfg)); | 
|  |  | 
|  | I3C_MasterGetDefaultConfig(&master_config); | 
|  |  | 
|  | master_config.baudRate_Hz.i2cBaud = ctrl_cfg->scl.i2c; | 
|  | master_config.baudRate_Hz.i3cPushPullBaud = ctrl_cfg->scl.i3c; | 
|  | master_config.enableOpenDrainHigh = dev_cfg->disable_open_drain_high_pp ? false : true; | 
|  |  | 
|  | if (dev_data->i3c_od_scl_hz) { | 
|  | master_config.baudRate_Hz.i3cOpenDrainBaud = dev_data->i3c_od_scl_hz; | 
|  | } | 
|  |  | 
|  | /* Initialize hardware */ | 
|  | I3C_MasterInit(base, &master_config, clock_freq); | 
|  |  | 
|  | out_configure: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Get configuration of the I3C hardware. | 
|  | * | 
|  | * This provides a way to get the current configuration of the I3C hardware. | 
|  | * | 
|  | * This can return cached config or probed hardware parameters, but it has to | 
|  | * be up to date with current configuration. | 
|  | * | 
|  | * @param[in] dev Pointer to controller device driver instance. | 
|  | * @param[in] type Type of configuration parameters being passed | 
|  | *                 in @p config. | 
|  | * @param[in,out] config Pointer to the configuration parameters. | 
|  | * | 
|  | * Note that if @p type is @c I3C_CONFIG_CUSTOM, @p config must contain | 
|  | * the ID of the parameter to be retrieved. | 
|  | * | 
|  | * @retval 0 If successful. | 
|  | * @retval -EIO General Input/Output errors. | 
|  | * @retval -ENOSYS If not implemented. | 
|  | */ | 
|  | static int mcux_i3c_config_get(const struct device *dev, | 
|  | enum i3c_config_type type, void *config) | 
|  | { | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | int ret = 0; | 
|  |  | 
|  | if ((type != I3C_CONFIG_CONTROLLER) || (config == NULL)) { | 
|  | ret = -EINVAL; | 
|  | goto out_configure; | 
|  | } | 
|  |  | 
|  | (void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config)); | 
|  |  | 
|  | out_configure: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Initialize the hardware. | 
|  | * | 
|  | * @param dev Pointer to controller device driver instance. | 
|  | */ | 
|  | static int mcux_i3c_init(const struct device *dev) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | struct i3c_config_controller *ctrl_config = &data->common.ctrl_config; | 
|  | i3c_master_config_t ctrl_config_hal; | 
|  | int ret = 0; | 
|  |  | 
|  | ret = i3c_addr_slots_init(dev); | 
|  | if (ret != 0) { | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); | 
|  | if (ret != 0) { | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | k_mutex_init(&data->lock); | 
|  | k_condvar_init(&data->condvar); | 
|  | k_sem_init(&data->device_sync_sem, 0, K_SEM_MAX_LIMIT); | 
|  |  | 
|  | I3C_MasterGetDefaultConfig(&ctrl_config_hal); | 
|  |  | 
|  | /* Set default SCL clock rate (in Hz) */ | 
|  | if (ctrl_config->scl.i2c == 0U) { | 
|  | ctrl_config->scl.i2c = ctrl_config_hal.baudRate_Hz.i2cBaud; | 
|  | } | 
|  |  | 
|  | if (ctrl_config->scl.i3c == 0U) { | 
|  | ctrl_config->scl.i3c = ctrl_config_hal.baudRate_Hz.i3cPushPullBaud; | 
|  | } | 
|  |  | 
|  | /* Currently can only act as primary controller. */ | 
|  | ctrl_config->is_secondary = false; | 
|  |  | 
|  | /* HDR mode not supported at the moment. */ | 
|  | ctrl_config->supported_hdr = 0U; | 
|  |  | 
|  | ret = mcux_i3c_configure(dev, I3C_CONFIG_CONTROLLER, ctrl_config); | 
|  | if (ret != 0) { | 
|  | ret = -EINVAL; | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | /* Disable all interrupts except error interrupt */ | 
|  | base->MINTCLR = I3C_MINTCLR_SLVSTART_MASK | | 
|  | I3C_MINTCLR_MCTRLDONE_MASK | | 
|  | I3C_MINTCLR_COMPLETE_MASK | | 
|  | I3C_MINTCLR_RXPEND_MASK | | 
|  | I3C_MINTCLR_TXNOTFULL_MASK | | 
|  | I3C_MINTCLR_IBIWON_MASK | | 
|  | I3C_MINTCLR_NOWMASTER_MASK; | 
|  |  | 
|  | /* Enable error interrupt */ | 
|  | base->MINTSET = I3C_MSTATUS_ERRWARN_MASK; | 
|  |  | 
|  | /* Just in case the bus is not in idle. */ | 
|  | ret = mcux_i3c_recover_bus(dev); | 
|  | if (ret != 0) { | 
|  | ret = -EIO; | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | /* Configure interrupt */ | 
|  | config->irq_config_func(dev); | 
|  |  | 
|  | /* Perform bus initialization */ | 
|  | ret = i3c_bus_init(dev, &config->common.dev_list); | 
|  |  | 
|  | err_out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int mcux_i3c_i2c_api_configure(const struct device *dev, uint32_t dev_config) | 
|  | { | 
|  | return -ENOSYS; | 
|  | } | 
|  |  | 
|  | static int mcux_i3c_i2c_api_transfer(const struct device *dev, | 
|  | struct i2c_msg *msgs, | 
|  | uint8_t num_msgs, | 
|  | uint16_t addr) | 
|  | { | 
|  | const struct mcux_i3c_config *config = dev->config; | 
|  | struct mcux_i3c_data *dev_data = dev->data; | 
|  | I3C_Type *base = config->base; | 
|  | int ret; | 
|  |  | 
|  | k_mutex_lock(&dev_data->lock, K_FOREVER); | 
|  |  | 
|  | mcux_i3c_wait_idle(dev_data, base); | 
|  |  | 
|  | mcux_i3c_xfer_reset(base); | 
|  |  | 
|  | /* Iterate over all the messages */ | 
|  | for (int i = 0; i < num_msgs; i++) { | 
|  | bool is_read = (msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ; | 
|  | bool no_ending = false; | 
|  |  | 
|  | /* | 
|  | * Emit start if this is the first message or that | 
|  | * the RESTART flag is set in message. | 
|  | */ | 
|  | bool emit_start = (i == 0) || | 
|  | ((msgs[i].flags & I2C_MSG_RESTART) == I2C_MSG_RESTART); | 
|  |  | 
|  | bool emit_stop = (msgs[i].flags & I2C_MSG_STOP) == I2C_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 & I2C_MSG_RW_MASK) == I2C_MSG_WRITE; | 
|  | bool next_is_restart = | 
|  | ((msgs[i + 1].flags & I2C_MSG_RESTART) == I2C_MSG_RESTART); | 
|  |  | 
|  | if (next_is_write && !next_is_restart) { | 
|  | no_ending = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = mcux_i3c_do_one_xfer(base, dev_data, addr, true, | 
|  | msgs[i].buf, msgs[i].len, | 
|  | is_read, emit_start, emit_stop, no_ending); | 
|  | if (ret < 0) { | 
|  | goto out_xfer_i2c_stop_unlock; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = 0; | 
|  |  | 
|  | out_xfer_i2c_stop_unlock: | 
|  | mcux_i3c_request_emit_stop(dev_data, base, true); | 
|  | mcux_i3c_errwarn_clear_all_nowait(base); | 
|  | mcux_i3c_status_clear_all(base); | 
|  | k_mutex_unlock(&dev_data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(i3c, mcux_i3c_driver_api) = { | 
|  | .i2c_api.configure = mcux_i3c_i2c_api_configure, | 
|  | .i2c_api.transfer = mcux_i3c_i2c_api_transfer, | 
|  | .i2c_api.recover_bus = mcux_i3c_recover_bus, | 
|  | #ifdef CONFIG_I2C_RTIO | 
|  | .i2c_api.iodev_submit = i2c_iodev_submit_fallback, | 
|  | #endif | 
|  |  | 
|  | .configure = mcux_i3c_configure, | 
|  | .config_get = mcux_i3c_config_get, | 
|  |  | 
|  | .recover_bus = mcux_i3c_recover_bus, | 
|  |  | 
|  | .do_daa = mcux_i3c_do_daa, | 
|  | .do_ccc = mcux_i3c_do_ccc, | 
|  |  | 
|  | .i3c_device_find = mcux_i3c_device_find, | 
|  |  | 
|  | .i3c_xfers = mcux_i3c_transfer, | 
|  |  | 
|  | #ifdef CONFIG_I3C_USE_IBI | 
|  | .ibi_enable = mcux_i3c_ibi_enable, | 
|  | .ibi_disable = mcux_i3c_ibi_disable, | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_I3C_RTIO | 
|  | .iodev_submit = i3c_iodev_submit_fallback, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | #define I3C_MCUX_DEVICE(id)							\ | 
|  | PINCTRL_DT_INST_DEFINE(id);						\ | 
|  | static void mcux_i3c_config_func_##id(const struct device *dev);	\ | 
|  | static struct i3c_device_desc mcux_i3c_device_array_##id[] =		\ | 
|  | I3C_DEVICE_ARRAY_DT_INST(id);					\ | 
|  | static struct i3c_i2c_device_desc mcux_i3c_i2c_device_array_##id[] =	\ | 
|  | I3C_I2C_DEVICE_ARRAY_DT_INST(id);				\ | 
|  | static const struct mcux_i3c_config mcux_i3c_config_##id = {		\ | 
|  | .base = (I3C_Type *) DT_INST_REG_ADDR(id),			\ | 
|  | .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(id)),		\ | 
|  | .clock_subsys =							\ | 
|  | (clock_control_subsys_t)DT_INST_CLOCKS_CELL(id, name),	\ | 
|  | .irq_config_func = mcux_i3c_config_func_##id,			\ | 
|  | .common.dev_list.i3c = mcux_i3c_device_array_##id,			\ | 
|  | .common.dev_list.num_i3c = ARRAY_SIZE(mcux_i3c_device_array_##id),	\ | 
|  | .common.dev_list.i2c = mcux_i3c_i2c_device_array_##id,			\ | 
|  | .common.dev_list.num_i2c = ARRAY_SIZE(mcux_i3c_i2c_device_array_##id),	\ | 
|  | .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id),			\ | 
|  | .disable_open_drain_high_pp =					\ | 
|  | DT_INST_PROP(id, disable_open_drain_high_pp),		\ | 
|  | };									\ | 
|  | static struct mcux_i3c_data mcux_i3c_data_##id = {			\ | 
|  | .i3c_od_scl_hz = DT_INST_PROP_OR(id, i3c_od_scl_hz, 0),		\ | 
|  | .common.ctrl_config.scl.i3c = DT_INST_PROP_OR(id, i3c_scl_hz, 0),	\ | 
|  | .common.ctrl_config.scl.i2c = DT_INST_PROP_OR(id, i2c_scl_hz, 0),	\ | 
|  | };									\ | 
|  | DEVICE_DT_INST_DEFINE(id,						\ | 
|  | mcux_i3c_init,					\ | 
|  | NULL,						\ | 
|  | &mcux_i3c_data_##id,				\ | 
|  | &mcux_i3c_config_##id,				\ | 
|  | POST_KERNEL,					\ | 
|  | CONFIG_I3C_CONTROLLER_INIT_PRIORITY,		\ | 
|  | &mcux_i3c_driver_api);				\ | 
|  | static void mcux_i3c_config_func_##id(const struct device *dev)		\ | 
|  | {									\ | 
|  | IRQ_CONNECT(DT_INST_IRQN(id),					\ | 
|  | DT_INST_IRQ(id, priority),				\ | 
|  | mcux_i3c_isr,					\ | 
|  | DEVICE_DT_INST_GET(id),				\ | 
|  | 0);							\ | 
|  | irq_enable(DT_INST_IRQN(id));					\ | 
|  | };									\ | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(I3C_MCUX_DEVICE) |