/* * Copyright (c) 2025 ITE Technology Inc. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ite_it51xxx_i3cs #include #include LOG_MODULE_REGISTER(i3cs_it51xxx); #include #include #include #include #include #include #include #define BYTE_0(x) FIELD_GET(GENMASK(7, 0), x) #define BYTE_1(x) FIELD_GET(GENMASK(15, 8), x) #define BYTE_2(x) FIELD_GET(GENMASK(23, 16), x) #define BYTE_3(x) FIELD_GET(GENMASK(31, 24), x) /* used for tx/rx fifo base address setting */ #define FIFO_ADDR_LB(x) FIELD_GET(GENMASK(10, 3), x) #define FIFO_ADDR_HB(x) FIELD_GET(GENMASK(18, 11), x) #define I3CS05_CONFIG_1 0x05 #define ID_RANDOM BIT(0) #define I3CS07_CONFIG_2 0x07 #define I3CS_TARGET_ADDRESS(n) FIELD_PREP(GENMASK(7, 1), n) #define I3CS08_STATUS_0 0x08 #define BUS_IS_BUSY BIT(0) #define I3CS09_STATUS_1 0x09 #define INT_ERROR_WARNING BIT(7) #define INT_CCC BIT(6) #define INT_DYN_ADDR_CHANGE BIT(5) #define INT_RX_PENDING BIT(3) #define INT_STOP BIT(2) #define INT_ADDR_MATCHED BIT(1) #define I3CS0A_STATUS_2 0x0A #define EVENT_DETECT_MASK GENMASK(5, 4) #define INT_TARGET_RST BIT(3) #define INT_EVENT BIT(2) #define I3CS0B_STATUS_3 0x0B #define HJ_DISABLED BIT(3) #define IBI_DISABLED BIT(0) #define I3CS0C_CONTROL_0 0x0C #define EXTENDED_IBI_DATA BIT(3) #define I3CS_EVENT_SELECT(n) FIELD_PREP(GENMASK(1, 0), n) #define I3CS0D_CONTROL_1 0x0D #define I3CS0F_CONTROL_3 0x0F #define I3CS11_INTERRUPT_ENABLE_CTRL_0 0x11 #define I3CS14_DIRECT_TX_FIFO_BASE_ADDR_LB 0x14 #define I3CS15_DIRECT_TX_FIFO_BASE_ADDR_HB 0x15 #define I3CS16_DIRECT_RX_FIFO_BASE_ADDR_LB 0x16 #define I3CS17_DIRECT_RX_FIFO_BASE_ADDR_HB 0x17 #define I3CS1A_DIRECT_TX_LENGTH_LB 0x1A #define I3CS1B_DIRECT_TX_LENGTH_HB 0x1B #define I3CS1C_ERROR_WARNING_REG_0 0x1C #define INVALID_START BIT(4) #define CONTROLLER_TERMINATED BIT(3) #define TX_FIFO_UNDERRUN (BIT(2) | BIT(1)) #define RX_FIFO_OVERRUN BIT(0) #define I3CS1D_ERROR_WARNING_REG_1 0x1D #define S0_OR_S1_ERROR BIT(3) #define SDR_PARITY_ERROR BIT(0) #define I3CS2C_DATA_CTRL_0 0x2C #define FLUSH_TX_FIFO BIT(0) #define I3CS41_TX_RX_FIFO_BASE_ADDR_HB 0x41 #define I3CS42_TX_FIFO_BASE_ADDR_LB 0x42 #define I3CS43_RX_FIFO_BASE_ADDR_LB 0x43 #define I3CS45_RX_FIFO_READ_PTR 0x45 #define I3CS4A_TX_FIFO_SIZE 0x4A #define I3CS_TX_FIFO_SIZE_MASK GENMASK(3, 0) #define I3CS4D_CONTROL_REG_4 0x4D #define I3CS_DIRECT_MODE_AUTO_CLR_TX_CNT BIT(6) #define I3CS_DIRECT_MODE_ENABLE BIT(5) | BIT(4) #define I3CS4E_DIRECT_FIFO_STATUS 0x4E #define I3CS_DIRECT_TX_DONE BIT(1) #define I3CS_DIRECT_RX_DONE BIT(0) #define I3CS58_TX_FIFO_BYTE_COUNT_LB 0x58 #define I3CS59_TX_FIFO_BYTE_COUNT_HB 0x59 #define I3CS5A_RX_FIFO_BYTE_COUNT_LB 0x5A #define I3CS5B_RX_FIFO_BYTE_COUNT_HB 0x5B #define I3CS64_DYNAMIC_ADDRESS 0x64 #define DYNAMIC_ADDRESS(x) FIELD_GET(GENMASK(7, 1), x) #define DYNAMIC_ADDRESS_VALID BIT(0) #define I3CS68_MRL_SET_BY_CTRL_LB 0x68 #define I3CS69_MRL_SET_BY_CTRL_HB 0x69 #define I3CS6A_MWL_SET_BY_CTRL_LB 0x6A #define I3CS6B_MWL_SET_BY_CTRL_HB 0x6B #define I3CS6C_PRAT_NUMBER_0 0x6C #define I3CS6D_PRAT_NUMBER_1 0x6D #define I3CS6E_PRAT_NUMBER_2 0x6E #define I3CS6F_PRAT_NUMBER_3 0x6F #define I3CS71_DCR 0x71 #define I3CS72_BCR 0x72 #define I3CS76_TX_FIFO_READ_PTR 0x76 #define I3CS7A_RX_FIFO_SIZE 0x7A #define I3CS_RX_FIFO_SIZE_MASK GENMASK(3, 0) #define IBI_MDB_GROUP_MASK GENMASK(7, 5) #define IBI_MDB_GROUP_PENDING_READ_NOTI 5 #define IT51XXX_DIRECT_MODE_FIFO_SIZE 4096 #define IT51XXX_I3CS_MAX_MRL_MWL 0xFFF /* 4095 bytes */ enum it51xxx_i3cs_event_type { EVT_NORMAL_MODE = 0, EVT_IBI, EVT_CONTROL_REQUEST, EVT_HOT_JOIN, }; enum it51xxx_i3cs_request_event { NONE = 0, REQUEST_NOT_SENT, REQUEST_NACK_EVT, REQUEST_ACK_EVT, }; static const struct fifo_size_mapping_t { uint16_t fifo_size; uint8_t value; } fifo_size_table[5] = {[0] = {.fifo_size = 16, .value = 0x0}, [1] = {.fifo_size = 32, .value = 0x5}, [2] = {.fifo_size = 64, .value = 0x6}, [3] = {.fifo_size = 128, .value = 0x7}, [4] = {.fifo_size = 4096, .value = 0xC}}; struct it51xxx_i3cs_data { struct i3c_driver_data common; struct i3c_target_config *target_config; /* configuration parameters for I3C hardware to act as target device */ struct i3c_config_target config_target; #ifdef CONFIG_I3C_USE_IBI struct k_sem ibi_sync_sem; #endif /* CONFIG_I3C_USE_IBI */ struct k_mutex lock; struct { uint8_t tx_data[CONFIG_I3CS_IT51XXX_TX_FIFO_SIZE]; uint8_t rx_data[CONFIG_I3CS_IT51XXX_RX_FIFO_SIZE]; } fifo __aligned(IT51XXX_DIRECT_MODE_FIFO_SIZE); }; struct it51xxx_i3cs_config { /* common i3c driver config */ struct i3c_driver_config common; const struct pinctrl_dev_config *pcfg; mm_reg_t base; uint8_t io_channel; uint8_t vendor_info; struct { mm_reg_t addr; uint8_t bit_mask; } extern_enable; void (*irq_config_func)(const struct device *dev); LOG_INSTANCE_PTR_DECLARE(log); }; static inline bool rx_direct_mode_is_enabled(const struct device *dev) { struct it51xxx_i3cs_data *data = dev->data; return sizeof(data->fifo.rx_data) == IT51XXX_DIRECT_MODE_FIFO_SIZE; } static inline bool tx_direct_mode_is_enabled(const struct device *dev) { struct it51xxx_i3cs_data *data = dev->data; return sizeof(data->fifo.tx_data) == IT51XXX_DIRECT_MODE_FIFO_SIZE; } static inline uint16_t rx_byte_cnt_in_fifo(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; return (sys_read8(cfg->base + I3CS5B_RX_FIFO_BYTE_COUNT_HB) << 8) + sys_read8(cfg->base + I3CS5A_RX_FIFO_BYTE_COUNT_LB); } static inline uint16_t tx_byte_cnt_in_fifo(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; return (sys_read8(cfg->base + I3CS59_TX_FIFO_BYTE_COUNT_HB) << 8) + sys_read8(cfg->base + I3CS58_TX_FIFO_BYTE_COUNT_LB); } static inline void set_mrl_value(const struct device *dev, const uint16_t value) { const struct it51xxx_i3cs_config *cfg = dev->config; uint16_t mrl; mrl = (value > IT51XXX_I3CS_MAX_MRL_MWL) ? IT51XXX_I3CS_MAX_MRL_MWL : value; sys_write8(BYTE_0(mrl), cfg->base + I3CS68_MRL_SET_BY_CTRL_LB); sys_write8(BYTE_1(mrl), cfg->base + I3CS69_MRL_SET_BY_CTRL_HB); } static inline void set_mwl_value(const struct device *dev, const uint16_t value) { const struct it51xxx_i3cs_config *cfg = dev->config; uint16_t mwl; mwl = (value > IT51XXX_I3CS_MAX_MRL_MWL) ? IT51XXX_I3CS_MAX_MRL_MWL : value; sys_write8(BYTE_0(mwl), cfg->base + I3CS6A_MWL_SET_BY_CTRL_LB); sys_write8(BYTE_1(mwl), cfg->base + I3CS6B_MWL_SET_BY_CTRL_HB); } static int it51xxx_i3cs_prepare_tx_fifo(const struct device *dev, uint8_t *buf, uint16_t len) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; uint16_t tx_count; if (len > sizeof(data->fifo.tx_data)) { return -ENOSPC; } tx_count = tx_byte_cnt_in_fifo(dev); if (tx_count) { LOG_INST_WRN(cfg->log, "dropped the remaining %d bytes in the tx fifo", tx_count); } /* flush tx fifo */ sys_write8(sys_read8(cfg->base + I3CS2C_DATA_CTRL_0) | FLUSH_TX_FIFO, cfg->base + I3CS2C_DATA_CTRL_0); /* set tx length */ if (tx_direct_mode_is_enabled(dev)) { sys_write8(BYTE_0(len), cfg->base + I3CS1A_DIRECT_TX_LENGTH_LB); sys_write8(BYTE_1(len), cfg->base + I3CS1B_DIRECT_TX_LENGTH_HB); } else { sys_write8(len, cfg->base + I3CS76_TX_FIFO_READ_PTR); } /* fill tx fifo with data */ memcpy(data->fifo.tx_data, buf, len); return 0; } static int it51xxx_i3cs_target_register(const struct device *dev, struct i3c_target_config *tgt_cfg) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; if (!data->target_config) { data->target_config = tgt_cfg; chip_block_idle(); pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); } else { LOG_INST_WRN(cfg->log, "the target has already been registered"); } return 0; } static int it51xxx_i3cs_target_unregister(const struct device *dev, struct i3c_target_config *tgt_cfg) { ARG_UNUSED(tgt_cfg); const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; if (data->target_config) { data->target_config = NULL; /* Permit to enter power policy and idle mode. */ pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); chip_permit_idle(); } else { LOG_INST_WRN(cfg->log, "the target has not been registered"); } return 0; } static int it51xxx_i3cs_target_tx_write(const struct device *dev, uint8_t *buf, uint16_t len, uint8_t hdr_mode) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; int ret; if (!buf || len == 0) { LOG_INST_ERR(cfg->log, "null buffer or zero length"); return -EINVAL; } if (hdr_mode != 0) { LOG_INST_ERR(cfg->log, "unsupported hdr mode"); return -ENOTSUP; } if (len > sizeof(data->fifo.tx_data)) { LOG_INST_ERR(cfg->log, "invalid tx length(%d)", len); return -ENOSPC; } k_mutex_lock(&data->lock, K_FOREVER); ret = it51xxx_i3cs_prepare_tx_fifo(dev, buf, len); k_mutex_unlock(&data->lock); return ret ? ret : len; } static inline bool it51xxx_i3cs_dynamic_addr_valid(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; return ((sys_read8(cfg->base + I3CS64_DYNAMIC_ADDRESS) & DYNAMIC_ADDRESS_VALID) == DYNAMIC_ADDRESS_VALID); } #ifdef CONFIG_I3C_USE_IBI static inline bool it51xxx_i3cs_is_ibi_disable(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; return ((sys_read8(cfg->base + I3CS0B_STATUS_3) & IBI_DISABLED) == IBI_DISABLED); } static inline bool it51xxx_i3cs_is_hj_disable(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; return ((sys_read8(cfg->base + I3CS0B_STATUS_3) & HJ_DISABLED) == HJ_DISABLED); } static int it51xxx_i3cs_wait_to_complete(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; if (k_sem_take(&data->ibi_sync_sem, K_MSEC(CONFIG_I3CS_IT51XXX_IBI_TIMEOUT_MS)) != 0) { LOG_INST_ERR(cfg->log, "ibi event transmission timed out"); return -ETIMEDOUT; } return 0; } static int it51xxx_i3cs_target_ibi_raise(const struct device *dev, struct i3c_ibi *request) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; struct i3c_config_target *config_target = &data->config_target; int ret = 0; uint8_t reg_val; if (!request) { LOG_INST_ERR(cfg->log, "ibi request is null"); return -EINVAL; } k_mutex_lock(&data->lock, K_FOREVER); reg_val = sys_read8(cfg->base + I3CS08_STATUS_0); if (reg_val & BUS_IS_BUSY) { LOG_INST_ERR(cfg->log, "bus is busy"); ret = -EBUSY; goto out; } switch (request->ibi_type) { case I3C_IBI_TARGET_INTR: if (it51xxx_i3cs_is_ibi_disable(dev) || !it51xxx_i3cs_dynamic_addr_valid(dev)) { LOG_INST_ERR(cfg->log, "ibi is disabled or dynamic address is invalid"); ret = -EINVAL; goto out; } if (request->payload_len > sizeof(data->fifo.tx_data) + 1) { LOG_INST_ERR(cfg->log, "payload too large for ibi tir"); ret = -ENOMEM; goto out; } if (config_target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE && request->payload_len == 0) { LOG_INST_ERR(cfg->log, "ibi should be with payload"); ret = -EINVAL; goto out; } if (!(config_target->bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE) && request->payload_len != 0) { LOG_INST_ERR(cfg->log, "ibi should not be with payload"); ret = -EINVAL; goto out; } if (request->payload_len == 0) { LOG_INST_DBG(cfg->log, "send ibi without payload"); sys_write8(I3CS_EVENT_SELECT(EVT_IBI), cfg->base + I3CS0C_CONTROL_0); } else { /* set mandatory data byte */ sys_write8(request->payload[0], cfg->base + I3CS0D_CONTROL_1); if (request->payload_len > 1) { if (FIELD_GET(IBI_MDB_GROUP_MASK, request->payload[0]) == IBI_MDB_GROUP_PENDING_READ_NOTI) { /* since the fifo for ibi payload and pending data is * shared, the i3cs controller cannot issue an ibi with * pending data notification if the ibi payload size * exceeds 1. */ LOG_INST_ERR(cfg->log, "unsupported multiple payloads with " "pending read noti. group"); ret = -ENOTSUP; goto out; } ret = it51xxx_i3cs_prepare_tx_fifo(dev, &request->payload[1], request->payload_len - 1); if (ret) { goto out; } sys_write8(EXTENDED_IBI_DATA | I3CS_EVENT_SELECT(EVT_IBI), cfg->base + I3CS0C_CONTROL_0); } else { sys_write8(I3CS_EVENT_SELECT(EVT_IBI), cfg->base + I3CS0C_CONTROL_0); } } break; case I3C_IBI_HOTJOIN: if (it51xxx_i3cs_is_hj_disable(dev) || it51xxx_i3cs_dynamic_addr_valid(dev)) { LOG_INST_ERR(cfg->log, "hj is disabled or dynamic address is already assigned"); ret = -EINVAL; goto out; } sys_write8(I3CS_EVENT_SELECT(EVT_HOT_JOIN), cfg->base + I3CS0C_CONTROL_0); break; case I3C_IBI_CONTROLLER_ROLE_REQUEST: LOG_INST_ERR(cfg->log, "unsupported controller role request"); ret = -ENOTSUP; goto out; default: LOG_INST_ERR(cfg->log, "invalid ibi type(0x%x)", request->ibi_type); ret = -EINVAL; goto out; } if (it51xxx_i3cs_wait_to_complete(dev) != 0) { LOG_INST_WRN(cfg->log, "failed to issue ibi. maybe the controller is offline"); sys_write8(I3CS_EVENT_SELECT(EVT_NORMAL_MODE), cfg->base + I3CS0C_CONTROL_0); } out: k_mutex_unlock(&data->lock); return ret; } #endif /* CONFIG_I3C_USE_IBI */ static int it51xxx_i3cs_set_fifo_address(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; if (sizeof(data->fifo.rx_data) <= 128 && sizeof(data->fifo.tx_data) <= 128) { if (FIFO_ADDR_HB(((uint32_t)&data->fifo.tx_data)) != FIFO_ADDR_HB(((uint32_t)&data->fifo.rx_data))) { LOG_INST_ERR(cfg->log, "the msb of tx and rx fifo address should be the same"); return -EINVAL; } sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.rx_data), cfg->base + I3CS43_RX_FIFO_BASE_ADDR_LB); sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.tx_data), cfg->base + I3CS42_TX_FIFO_BASE_ADDR_LB); sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.tx_data), cfg->base + I3CS41_TX_RX_FIFO_BASE_ADDR_HB); return 0; } if (!rx_direct_mode_is_enabled(dev) || !tx_direct_mode_is_enabled(dev)) { /* The tx and rx direct modes must be enabled simultaneously. */ LOG_INST_ERR(cfg->log, "tx or rx fifo size is invalid for direct mode"); return -EINVAL; } LOG_INST_DBG(cfg->log, "direct mode is enabled"); sys_write8(sys_read8(cfg->base + I3CS4D_CONTROL_REG_4) | I3CS_DIRECT_MODE_ENABLE, cfg->base + I3CS4D_CONTROL_REG_4); sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.rx_data), cfg->base + I3CS16_DIRECT_RX_FIFO_BASE_ADDR_LB); sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.rx_data), cfg->base + I3CS17_DIRECT_RX_FIFO_BASE_ADDR_HB); sys_write8(FIFO_ADDR_LB((uint32_t)&data->fifo.tx_data), cfg->base + I3CS14_DIRECT_TX_FIFO_BASE_ADDR_LB); sys_write8(FIFO_ADDR_HB((uint32_t)&data->fifo.tx_data), cfg->base + I3CS15_DIRECT_TX_FIFO_BASE_ADDR_HB); return 0; } static int it51xxx_i3cs_init(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; struct i3c_config_target *config_target = &data->config_target; uint8_t reg_val; int ret; ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); if (ret != 0) { LOG_INST_ERR(cfg->log, "failed to apply pinctrl, ret %d", ret); return ret; } /* set i3cs channel selection */ sys_write8(cfg->io_channel, cfg->base + I3CS4D_CONTROL_REG_4); LOG_INST_DBG(cfg->log, "select io channel %d", cfg->io_channel); /* set extern enable bit */ if (cfg->extern_enable.bit_mask > 7) { LOG_INST_ERR(cfg->log, "invalid bit mask %d for extern enable setting", cfg->extern_enable.bit_mask); return -EINVAL; } sys_write8(sys_read8(cfg->extern_enable.addr) | BIT(cfg->extern_enable.bit_mask), cfg->extern_enable.addr); /* set static address */ sys_write8(I3CS_TARGET_ADDRESS(config_target->static_addr), cfg->base + I3CS07_CONFIG_2); /* set msb(vendor info) of get device status ccc */ sys_write8(cfg->vendor_info, cfg->base + I3CS0F_CONTROL_3); /* set pid, bcr and dcr */ if (config_target->pid_random) { sys_write8(sys_read8(cfg->base + I3CS05_CONFIG_1) | ID_RANDOM, cfg->base + I3CS05_CONFIG_1); sys_write8(BYTE_0(config_target->pid), cfg->base + I3CS6C_PRAT_NUMBER_0); sys_write8(BYTE_1(config_target->pid), cfg->base + I3CS6D_PRAT_NUMBER_1); sys_write8(BYTE_2(config_target->pid), cfg->base + I3CS6E_PRAT_NUMBER_2); sys_write8(BYTE_3(config_target->pid), cfg->base + I3CS6F_PRAT_NUMBER_3); LOG_INST_INF(cfg->log, "set pid random value: %#llx", config_target->pid); } if (I3C_BCR_DEVICE_ROLE(config_target->bcr) == I3C_BCR_DEVICE_ROLE_I3C_CONTROLLER_CAPABLE) { LOG_INST_ERR(cfg->log, "i3cs doesn't support controller capability"); return -ENOTSUP; } sys_write8(config_target->bcr, cfg->base + I3CS72_BCR); sys_write8(config_target->dcr, cfg->base + I3CS71_DCR); LOG_INST_INF(cfg->log, "tx fifo size(%d), address(0x%x)", sizeof(data->fifo.tx_data), (uint32_t)&data->fifo.tx_data); LOG_INST_INF(cfg->log, "rx fifo size(%d), address(0x%x)", sizeof(data->fifo.rx_data), (uint32_t)&data->fifo.rx_data); /* set tx and rx fifo size */ for (uint8_t i = 0; i <= ARRAY_SIZE(fifo_size_table); i++) { if (i == ARRAY_SIZE(fifo_size_table)) { LOG_INST_ERR(cfg->log, "unknown rx fifo size %d", sizeof(data->fifo.rx_data)); return -ENOTSUP; } if (sizeof(data->fifo.rx_data) == fifo_size_table[i].fifo_size) { sys_write8(FIELD_PREP(I3CS_RX_FIFO_SIZE_MASK, fifo_size_table[i].value), cfg->base + I3CS7A_RX_FIFO_SIZE); set_mwl_value(dev, sizeof(data->fifo.rx_data)); break; } } for (uint8_t i = 0; i <= ARRAY_SIZE(fifo_size_table); i++) { if (i == ARRAY_SIZE(fifo_size_table)) { LOG_INST_ERR(cfg->log, "unknown tx fifo size %d", sizeof(data->fifo.tx_data)); return -ENOTSUP; } if (sizeof(data->fifo.tx_data) == fifo_size_table[i].fifo_size) { sys_write8(FIELD_PREP(I3CS_TX_FIFO_SIZE_MASK, fifo_size_table[i].value), cfg->base + I3CS4A_TX_FIFO_SIZE); set_mrl_value(dev, sizeof(data->fifo.tx_data)); break; } } ret = it51xxx_i3cs_set_fifo_address(dev); if (ret) { return ret; } if (tx_direct_mode_is_enabled(dev)) { sys_write8(sys_read8(cfg->base + I3CS4D_CONTROL_REG_4) | I3CS_DIRECT_MODE_AUTO_CLR_TX_CNT, cfg->base + I3CS4D_CONTROL_REG_4); } #ifdef CONFIG_I3C_USE_IBI k_sem_init(&data->ibi_sync_sem, 0, 1); #endif /*CONFIG_I3C_USE_IBI */ k_mutex_init(&data->lock); /* clear interrupt/errwarn status and enable interrupt */ sys_write8(sys_read8(cfg->base + I3CS1C_ERROR_WARNING_REG_0), cfg->base + I3CS1C_ERROR_WARNING_REG_0); sys_write8(sys_read8(cfg->base + I3CS1D_ERROR_WARNING_REG_1), cfg->base + I3CS1D_ERROR_WARNING_REG_1); sys_write8(sys_read8(cfg->base + I3CS09_STATUS_1), cfg->base + I3CS09_STATUS_1); reg_val = INT_STOP | INT_ERROR_WARNING; sys_write8(reg_val, cfg->base + I3CS11_INTERRUPT_ENABLE_CTRL_0); cfg->irq_config_func(dev); return 0; } static DEVICE_API(i3c, it51xxx_i3cs_api) = { .target_tx_write = it51xxx_i3cs_target_tx_write, .target_register = it51xxx_i3cs_target_register, .target_unregister = it51xxx_i3cs_target_unregister, #ifdef CONFIG_I3C_USE_IBI .ibi_raise = it51xxx_i3cs_target_ibi_raise, #endif /* CONFIG_I3C_USE_IBI */ }; static void it51xxx_i3cs_check_errwarn(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; uint8_t errwarn0_val, errwarn1_val; errwarn0_val = sys_read8(cfg->base + I3CS1C_ERROR_WARNING_REG_0); errwarn1_val = sys_read8(cfg->base + I3CS1D_ERROR_WARNING_REG_1); if (errwarn0_val & INVALID_START) { LOG_INST_ERR(cfg->log, "isr: invalid start"); } if (errwarn0_val & CONTROLLER_TERMINATED) { LOG_INST_WRN(cfg->log, "isr: terminated by controller, flush the remaining %d bytes", tx_byte_cnt_in_fifo(dev)); sys_write8(sys_read8(cfg->base + I3CS2C_DATA_CTRL_0) | FLUSH_TX_FIFO, cfg->base + I3CS2C_DATA_CTRL_0); } if (errwarn0_val & TX_FIFO_UNDERRUN) { LOG_INST_ERR(cfg->log, "isr: the tx fifo is underrun"); } if (errwarn0_val & RX_FIFO_OVERRUN) { LOG_INST_ERR(cfg->log, "isr: the rx fifo is overrun"); } if (errwarn1_val & S0_OR_S1_ERROR) { LOG_INST_ERR(cfg->log, "isr: s0 or s1 error is detected"); } if (errwarn1_val & SDR_PARITY_ERROR) { LOG_INST_ERR(cfg->log, "isr: sdr parity error"); } LOG_INST_DBG(cfg->log, "isr: error/warning is detected(0x%x, 0x%x)", errwarn0_val, errwarn1_val); /* write 1 to clear the error and warning registers */ sys_write8(errwarn0_val, cfg->base + I3CS1C_ERROR_WARNING_REG_0); sys_write8(errwarn1_val, cfg->base + I3CS1D_ERROR_WARNING_REG_1); } static inline void invoke_rx_cb(const struct device *dev, const bool ccc, uint8_t *buf, const size_t buf_len) { struct it51xxx_i3cs_data *data = dev->data; const struct i3c_target_callbacks *target_cb = data->target_config ? data->target_config->callbacks : NULL; if (!ccc) { LOG_HEXDUMP_DBG(buf, buf_len, "isr: rx:"); #ifdef CONFIG_I3C_TARGET_BUFFER_MODE if (target_cb && target_cb->buf_write_received_cb) { target_cb->buf_write_received_cb(data->target_config, buf, buf_len); } #endif /* CONFIG_I3C_TARGET_BUFFER_MODE */ } else { LOG_HEXDUMP_WRN(buf, buf_len, "isr: unhandled ccc:"); } } static void it51xxx_i3cs_process_rx_fifo(const struct device *dev, const bool ccc) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; uint16_t byte_count = rx_byte_cnt_in_fifo(dev); if (rx_direct_mode_is_enabled(dev)) { uint8_t dfifo_status = sys_read8(cfg->base + I3CS4E_DIRECT_FIFO_STATUS); if (dfifo_status & I3CS_DIRECT_RX_DONE) { sys_write8(I3CS_DIRECT_RX_DONE, cfg->base + I3CS4E_DIRECT_FIFO_STATUS); } else { LOG_INST_WRN(cfg->log, "isr: rx pending, but rx not completed"); return; } invoke_rx_cb(dev, ccc, data->fifo.rx_data, byte_count); } else { uint8_t read_ptr, idx; uint8_t rx_buf[sizeof(data->fifo.rx_data)]; const size_t rx_fifo_sz = sizeof(data->fifo.rx_data); read_ptr = sys_read8(cfg->base + I3CS45_RX_FIFO_READ_PTR); idx = read_ptr % rx_fifo_sz; for (size_t i = 0; i < byte_count; i++) { rx_buf[i] = data->fifo.rx_data[(idx + i) >= rx_fifo_sz ? (idx + i) - rx_fifo_sz : (idx + i)]; } sys_write8(read_ptr + byte_count, cfg->base + I3CS45_RX_FIFO_READ_PTR); invoke_rx_cb(dev, ccc, rx_buf, byte_count); } } static void it51xxx_i3cs_process_tx_fifo(const struct device *dev, const bool ccc) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; const struct i3c_target_callbacks *target_cb = data->target_config ? data->target_config->callbacks : NULL; if (tx_direct_mode_is_enabled(dev)) { uint8_t dfifo_status = sys_read8(cfg->base + I3CS4E_DIRECT_FIFO_STATUS); if (dfifo_status & I3CS_DIRECT_TX_DONE) { sys_write8(I3CS_DIRECT_TX_DONE, cfg->base + I3CS4E_DIRECT_FIFO_STATUS); } else { return; } } if (!ccc) { #ifdef CONFIG_I3C_TARGET_BUFFER_MODE if (target_cb && target_cb->buf_read_requested_cb) { target_cb->buf_read_requested_cb(data->target_config, NULL, NULL, NULL); } #endif /* CONFIG_I3C_TARGET_BUFFER_MODE */ } } static void it51xxx_i3cs_isr(const struct device *dev) { const struct it51xxx_i3cs_config *cfg = dev->config; struct it51xxx_i3cs_data *data = dev->data; const struct i3c_target_callbacks *target_cb = data->target_config ? data->target_config->callbacks : NULL; uint8_t int_status_1, int_status_2; int_status_1 = sys_read8(cfg->base + I3CS09_STATUS_1); int_status_2 = sys_read8(cfg->base + I3CS0A_STATUS_2); LOG_INST_DBG(cfg->log, "isr: interrupt status 0x%x 0x%x", int_status_1, int_status_2); if (int_status_1 & INT_DYN_ADDR_CHANGE) { if (it51xxx_i3cs_dynamic_addr_valid(dev)) { if (data->target_config) { data->target_config->address = DYNAMIC_ADDRESS( sys_read8(cfg->base + I3CS64_DYNAMIC_ADDRESS)); } LOG_INST_DBG(cfg->log, "dynamic address is assigned"); } else { if (data->target_config) { data->target_config->address = 0; } LOG_INST_DBG(cfg->log, "dynamic address is reset"); } } if (int_status_1 & INT_ERROR_WARNING) { it51xxx_i3cs_check_errwarn(dev); } if (int_status_1 & INT_STOP) { bool is_unhandled_ccc = (!(int_status_1 & INT_ADDR_MATCHED) || (int_status_1 & INT_CCC)); if (int_status_1 & INT_RX_PENDING) { it51xxx_i3cs_process_rx_fifo(dev, is_unhandled_ccc); } else { it51xxx_i3cs_process_tx_fifo(dev, is_unhandled_ccc); } if (!is_unhandled_ccc) { if (target_cb != NULL && target_cb->stop_cb) { target_cb->stop_cb(data->target_config); } } } switch (int_status_2 & EVENT_DETECT_MASK) { case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_NACK_EVT): LOG_INST_ERR(cfg->log, "isr: nack is detected"); break; case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_NOT_SENT): LOG_INST_ERR(cfg->log, "isr: request is not sent yet"); break; case FIELD_PREP(EVENT_DETECT_MASK, REQUEST_ACK_EVT): if (int_status_2 & INT_EVENT) { LOG_INST_DBG(cfg->log, "isr: tir/hj is completed"); } break; default: break; }; if (int_status_2 & EVENT_DETECT_MASK) { #ifdef CONFIG_I3C_USE_IBI k_sem_give(&data->ibi_sync_sem); #endif /* CONFIG_I3C_USE_IBI */ } if (int_status_2 & INT_TARGET_RST) { LOG_INST_INF(cfg->log, "isr: target reset pattern is detected"); } sys_write8(int_status_1, cfg->base + I3CS09_STATUS_1); sys_write8(int_status_2, cfg->base + I3CS0A_STATUS_2); } #define IT51XXX_I3CS_EXTERN_ENABLE(n) \ { \ .addr = DT_INST_PROP_BY_IDX(n, extern_enable, 0), \ .bit_mask = DT_INST_PROP_BY_IDX(n, extern_enable, 1), \ } #define IT51XXX_I3CS_INIT(n) \ LOG_INSTANCE_REGISTER(DT_NODE_FULL_NAME_TOKEN(DT_DRV_INST(n)), n, \ CONFIG_I3C_IT51XXX_LOG_LEVEL); \ PINCTRL_DT_INST_DEFINE(n); \ static void it51xxx_i3cs_config_func_##n(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), 0, it51xxx_i3cs_isr, DEVICE_DT_INST_GET(n), 0); \ irq_enable(DT_INST_IRQN(n)); \ }; \ static const struct it51xxx_i3cs_config i3c_config_##n = { \ .base = DT_INST_REG_ADDR(n), \ .irq_config_func = it51xxx_i3cs_config_func_##n, \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ .io_channel = DT_INST_PROP(n, io_channel), \ .extern_enable = IT51XXX_I3CS_EXTERN_ENABLE(n), \ .vendor_info = DT_INST_PROP_OR(n, vendor_info_fields, 0x0), \ LOG_INSTANCE_PTR_INIT(log, DT_NODE_FULL_NAME_TOKEN(DT_DRV_INST(n)), n)}; \ static struct it51xxx_i3cs_data i3c_data_##n = { \ .config_target.static_addr = DT_INST_PROP_OR(n, static_address, 0), \ .config_target.pid = DT_INST_PROP_OR(n, pid_random_value, 0), \ .config_target.pid_random = DT_INST_NODE_HAS_PROP(n, pid_random_value), \ .config_target.bcr = DT_INST_PROP_OR(n, bcr, 0x0F), \ .config_target.dcr = DT_INST_PROP_OR(n, dcr, 0), \ .config_target.supported_hdr = false, \ }; \ DEVICE_DT_INST_DEFINE(n, it51xxx_i3cs_init, NULL, &i3c_data_##n, &i3c_config_##n, \ POST_KERNEL, CONFIG_I3C_CONTROLLER_INIT_PRIORITY, \ &it51xxx_i3cs_api); DT_INST_FOREACH_STATUS_OKAY(IT51XXX_I3CS_INIT)