| /* |
| * Copyright (c) 2025 ITE Technology Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ite_it51xxx_i3cm |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/logging/log_instance.h> |
| LOG_MODULE_REGISTER(i3cm_it51xxx); |
| |
| #include <zephyr/drivers/i3c.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/device.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/pm/policy.h> |
| |
| #include <soc_common.h> |
| |
| #define BYTE_0(x) FIELD_GET(GENMASK(7, 0), x) |
| #define BYTE_1(x) FIELD_GET(GENMASK(15, 8), x) |
| |
| #define CALC_FREQUENCY(t_low, t_hddat, t_high) \ |
| (NSEC_PER_SEC / (t_high + t_low + t_hddat + 3) / 208 * 10) |
| |
| /* it51xxx i3cm registers definition */ |
| #define I3CM00_CYCLE_TYPE 0x00 |
| #define MORE_I3CM_TRANSFER BIT(6) |
| #define I3CM_PRIV_TRANS_WITHOUT_7EH_ADDR BIT(5) |
| #define I3CM_CYCLE_TYPE_SELECT(n) FIELD_PREP(GENMASK(3, 0), n) |
| |
| #define I3CM01_STATUS 0x01 |
| #define START_TRANSFER BIT(7) |
| #define PARITY_ERROR BIT(5) |
| #define CRC5_ERROR BIT(4) |
| #define IBI_INTERRUPT BIT(3) |
| #define TARGET_NACK BIT(2) |
| #define TRANSFER_END BIT(1) |
| #define NEXT_TRANSFER BIT(0) |
| |
| #define I3CM02_TARGET_ADDRESS 0x02 |
| #define I3CM_TARGET_ADDRESS(n) FIELD_PREP(GENMASK(7, 1), n) |
| |
| #define I3CM03_COMMON_COMMAND_CODE 0x03 |
| #define I3CM04_WRITE_LENGTH_LB 0x04 |
| #define I3CM05_WRITE_LENGTH_HB 0x05 |
| #define I3CM06_READ_LENGTH_LB 0x06 |
| #define I3CM07_READ_LENGTH_HB 0x07 |
| #define I3CM0E_DATA_COUNT_LB 0x0E |
| #define I3CM0F_IBI_ADDRESS 0x0F |
| #define I3CM_IBI_ADDR_MASK GENMASK(7, 1) |
| #define I3CM_IBI_RNW BIT(0) |
| |
| #define I3CM10_CONTROL 0x10 |
| #define I3CM_INTERRUPT_ENABLE BIT(7) |
| #define I3CM_REFUSE_IBI BIT(0) |
| |
| #define I3CM15_CONTROL_2 0x15 |
| #define I3CM_CCC_WITH_DEFINING_BYTE BIT(0) |
| |
| #define I3CM16_CCC_DEFINING_BYTE 0x16 |
| #define I3CM1E_DATA_COUNT_HB 0x1E |
| #define I3CM20_TCAS 0x20 /* i3c: clock after start condition */ |
| #define I3CM21_TCBP 0x21 /* i3c: clock before stop condition */ |
| #define I3CM22_TCBSR 0x22 /* i3c: clock before repeated start condition */ |
| #define I3CM23_TCASR 0x23 /* i3c: clock after repeated start condition */ |
| #define I3CM24_THDDAT_LB 0x24 /* i3c: low byte of data hold time */ |
| #define I3CM26_TLOW_LB 0x26 /* i3c: low byte of scl clock low period */ |
| #define I3CM27_TLOW_HB 0x27 /* i3c: high byte of scl clock low period */ |
| #define I3CM28_THIGH_LB 0x28 /* i3c: low byte of scl clock high period */ |
| #define I3CM29_THIGH_HB 0x29 /* i3c: high byte of scl clock high period */ |
| #define I3CM2A_TLOW_OD_LB 0x2A /* i3c: low byte of open drain scl clock low period */ |
| #define I3CM2B_TLOW_OD_HB 0x2B /* i3c: high byte of open drain scl clock low period */ |
| #define I3CM2F_I2C_CONTROL 0x2F |
| #define I3CM_USE_I2C_TIMING_SETTING BIT(1) |
| |
| #define I3CM30_I2C_THDSTA_SUSTO_LB \ |
| 0x30 /* i2c: low byte of "hold time for a (repeated) start"/"setup time for stop" */ |
| #define I3CM31_I2C_THDSTA_SUSTO_HB \ |
| 0x31 /* i2c: high byte of "hold time for a (repeated) start"/"setup time for stop" */ |
| #define I3CM34_I2C_THDDAT_LB 0x34 /* i2c: low byte of data hold time */ |
| #define I3CM35_I2C_THDDAT_HB 0x35 /* i2c: high byte of data hold time */ |
| #define I3CM36_I2C_TLOW_LB 0x36 /* i2c: low byte of scl clock low period */ |
| #define I3CM37_I2C_TLOW_HB 0x37 /* i2c: high byte of scl clock low period */ |
| #define I3CM38_I2C_THIGH_LB 0x38 /* i2c: low byte of scl clock high period */ |
| #define I3CM39_I2C_THIGH_HB 0x39 /* i2c: high byte of scl clock high period */ |
| |
| #define I3CM50_CONTROL_3 0x50 |
| #define I3CM_DLM_SIZE_MASK GENMASK(5, 4) |
| #define I3CM_CHANNEL_SELECT_MASK GENMASK(3, 2) |
| #define I3CM_PULL_UP_RESISTOR BIT(1) |
| #define I3CM_ENABLE BIT(0) |
| |
| #define I3CM52_DLM_BASE_ADDRESS_LB 0x52 /* dlm base address[15:8] */ |
| #define I3CM53_DLM_BASE_ADDRESS_HB 0x53 /* dlm base address[17:16] */ |
| |
| #define I3C_IBI_HJ_ADDR 0x02 |
| |
| #define I3C_BUS_TLOW_PP_MIN_NS 24 /* T_LOW period in push-pull mode */ |
| #define I3C_BUS_THIGH_PP_MIN_NS 24 /* T_HIGH period in push-pull mode */ |
| #define I3C_BUS_TLOW_OD_MIN_NS 200 /* T_LOW period in open-drain mode */ |
| |
| enum it51xxx_cycle_types { |
| PRIVATE_WRITE_TRANSFER = 0, |
| PRIVATE_READ_TRANSFER, |
| PRIVATE_WRITE_READ_TRANSFER, |
| LEGACY_I2C_WRITE_TRANSFER, |
| LEGACY_I2C_READ_TRANSFER, |
| LEGACY_I2C_WRITE_READ_TRANSFER, |
| BROADCAST_CCC_WRITE_TRANSFER, |
| DIRECT_CCC_WRITE_TRANSFER, |
| DIRECT_CCC_READ_TRANSFER, |
| DAA_TRANSFER, |
| IBI_READ_TRANSFER, |
| HDR_TRANSFER, |
| }; |
| |
| enum it51xxx_message_state { |
| IT51XXX_I3CM_MSG_IDLE = 0, |
| IT51XXX_I3CM_MSG_BROADCAST_CCC, |
| IT51XXX_I3CM_MSG_DIRECT_CCC, |
| IT51XXX_I3CM_MSG_DAA, |
| IT51XXX_I3CM_MSG_PRIVATE_XFER, |
| IT51XXX_I3CM_MSG_IBI, |
| IT51XXX_I3CM_MSG_ABORT, |
| IT51XXX_I3CM_MSG_ERROR, |
| }; |
| |
| struct it51xxx_i3cm_data { |
| /* common i3c driver data */ |
| struct i3c_driver_data common; |
| |
| enum it51xxx_message_state msg_state; |
| |
| struct { |
| struct i3c_ccc_payload *payload; |
| size_t target_idx; |
| } ccc_msgs; |
| |
| struct { |
| uint8_t target_addr; |
| |
| uint8_t num_msgs; |
| uint8_t curr_idx; |
| struct i3c_msg *i3c_msgs; |
| struct i2c_msg *i2c_msgs; |
| } curr_msg; |
| |
| #ifdef CONFIG_I3C_USE_IBI |
| bool ibi_hj_response; |
| uint8_t ibi_target_addr; |
| |
| struct { |
| uint8_t addr[4]; |
| uint8_t num_addr; |
| } ibi; |
| #endif /* CONFIG_I3C_USE_IBI */ |
| |
| struct k_sem msg_sem; |
| struct k_mutex lock; |
| |
| bool is_initialized; |
| bool error_is_detected; |
| bool transfer_is_aborted; /* record that the transfer was aborted due to ibi transaction. */ |
| |
| struct { |
| uint8_t tx_data[CONFIG_I3CM_IT51XXX_DLM_SIZE / 2]; |
| uint8_t rx_data[CONFIG_I3CM_IT51XXX_DLM_SIZE / 2]; |
| } dlm_data __aligned(CONFIG_I3CM_IT51XXX_DLM_SIZE); |
| }; |
| |
| struct it51xxx_i3cm_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 irq_num; |
| |
| struct { |
| uint8_t i3c_pp_duty_cycle; |
| uint32_t i3c_od_scl_hz; |
| uint32_t i3c_scl_hddat; |
| uint32_t i3c_scl_tcas; |
| uint32_t i3c_scl_tcbs; |
| uint32_t i3c_scl_tcasr; |
| uint32_t i3c_scl_tcbsr; |
| uint32_t i2c_scl_hddat; |
| } clocks; |
| |
| void (*irq_config_func)(const struct device *dev); |
| |
| LOG_INSTANCE_PTR_DECLARE(log); |
| }; |
| |
| static inline bool bus_is_idle(const struct device *dev) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| return (data->msg_state == IT51XXX_I3CM_MSG_IDLE); |
| } |
| |
| static int it51xxx_curr_msg_init(const struct device *dev, struct i3c_msg *i3c_msgs, |
| struct i2c_msg *i2c_msgs, uint8_t num_msgs, uint8_t tgt_addr) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| __ASSERT(!(i3c_msgs == NULL && i2c_msgs == NULL), "both i3c_msgs and i2c_msgs are null"); |
| __ASSERT(!(i3c_msgs != NULL && i2c_msgs != NULL), |
| "both i3c_msgs and i2c_msgs are not null"); |
| |
| data->curr_msg.target_addr = tgt_addr; |
| data->curr_msg.num_msgs = num_msgs; |
| data->curr_msg.curr_idx = 0; |
| data->curr_msg.i3c_msgs = i3c_msgs; |
| data->curr_msg.i2c_msgs = i2c_msgs; |
| |
| return 0; |
| } |
| |
| static void it51xxx_enable_standby_state(const struct device *dev, const bool enable) |
| { |
| ARG_UNUSED(dev); |
| |
| if (enable) { |
| chip_permit_idle(); |
| pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } else { |
| chip_block_idle(); |
| pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } |
| } |
| |
| static inline int it51xxx_set_tx_rx_length(const struct device *dev, const size_t tx_len, |
| const size_t rx_len) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| |
| if (tx_len > (CONFIG_I3CM_IT51XXX_DLM_SIZE / 2) || |
| rx_len > (CONFIG_I3CM_IT51XXX_DLM_SIZE / 2)) { |
| LOG_INST_ERR(cfg->log, "invalid tx(%d) or rx(%d) length", tx_len, rx_len); |
| return -EINVAL; |
| } |
| |
| sys_write8(BYTE_0(rx_len), cfg->base + I3CM06_READ_LENGTH_LB); |
| sys_write8(BYTE_1(rx_len), cfg->base + I3CM07_READ_LENGTH_HB); |
| sys_write8(BYTE_0(tx_len), cfg->base + I3CM04_WRITE_LENGTH_LB); |
| sys_write8(BYTE_1(tx_len), cfg->base + I3CM05_WRITE_LENGTH_HB); |
| |
| return 0; |
| } |
| |
| static inline size_t it51xxx_get_received_data_count(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| |
| return sys_read8(cfg->base + I3CM0E_DATA_COUNT_LB) + |
| ((sys_read8(cfg->base + I3CM1E_DATA_COUNT_HB) & 0x03) << 8); |
| } |
| |
| static void it51xxx_set_op_type(const struct device *dev, const uint8_t cycle_type, |
| const bool more_transfer, const bool broadcast_address) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| uint8_t reg_val = 0x0; |
| |
| if (more_transfer) { |
| reg_val |= MORE_I3CM_TRANSFER; |
| } |
| if (!broadcast_address) { |
| reg_val |= I3CM_PRIV_TRANS_WITHOUT_7EH_ADDR; |
| } |
| reg_val |= I3CM_CYCLE_TYPE_SELECT(cycle_type); |
| sys_write8(reg_val, cfg->base + I3CM00_CYCLE_TYPE); |
| LOG_INST_DBG(cfg->log, "set cycle type(%d) %s broadcast address %s", cycle_type, |
| broadcast_address ? "with" : "without", |
| more_transfer ? "and more transfer flag" : ""); |
| } |
| |
| static int it51xxx_wait_to_complete(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret = 0; |
| |
| if (k_sem_take(&data->msg_sem, K_MSEC(CONFIG_I3CM_IT51XXX_TRANSFER_TIMEOUT_MS)) != 0) { |
| LOG_INST_ERR(cfg->log, "timeout: message status(%d)", data->msg_state); |
| ret = -ETIMEDOUT; |
| } |
| |
| irq_disable(cfg->irq_num); |
| if (data->transfer_is_aborted) { |
| data->transfer_is_aborted = false; |
| ret = -EBUSY; |
| } |
| if (data->error_is_detected) { |
| data->error_is_detected = false; |
| ret = -EIO; |
| } |
| irq_enable(cfg->irq_num); |
| |
| return ret; |
| } |
| |
| static bool it51xxx_curr_msg_is_i3c(const struct device *dev) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| return (data->curr_msg.i3c_msgs != NULL); |
| } |
| |
| static int it51xxx_start_i3c_i2c_private_xfer(const struct device *dev, const uint8_t cycle_type, |
| const uint8_t dynamic_addr, const bool more_transfer, |
| const bool broadcast_address) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i2c_msg *i2c_msgs = data->curr_msg.i2c_msgs; |
| struct i3c_msg *i3c_msgs = data->curr_msg.i3c_msgs; |
| size_t tx_length = 0, rx_length = 0; |
| int ret; |
| |
| switch (cycle_type) { |
| case LEGACY_I2C_WRITE_TRANSFER: |
| __fallthrough; |
| case PRIVATE_WRITE_TRANSFER: |
| rx_length = 0; |
| tx_length = it51xxx_curr_msg_is_i3c(dev) ? i3c_msgs[data->curr_msg.curr_idx].len |
| : i2c_msgs[data->curr_msg.curr_idx].len; |
| break; |
| case LEGACY_I2C_READ_TRANSFER: |
| __fallthrough; |
| case PRIVATE_READ_TRANSFER: |
| rx_length = it51xxx_curr_msg_is_i3c(dev) ? i3c_msgs[data->curr_msg.curr_idx].len |
| : i2c_msgs[data->curr_msg.curr_idx].len; |
| tx_length = 0; |
| break; |
| case LEGACY_I2C_WRITE_READ_TRANSFER: |
| __fallthrough; |
| case PRIVATE_WRITE_READ_TRANSFER: |
| tx_length = it51xxx_curr_msg_is_i3c(dev) ? i3c_msgs[data->curr_msg.curr_idx].len |
| : i2c_msgs[data->curr_msg.curr_idx].len; |
| rx_length = it51xxx_curr_msg_is_i3c(dev) |
| ? i3c_msgs[data->curr_msg.curr_idx + 1].len |
| : i2c_msgs[data->curr_msg.curr_idx + 1].len; |
| break; |
| default: |
| LOG_INST_ERR(cfg->log, "unsupported cycle type(0x%x)", cycle_type); |
| return -ENOTSUP; |
| } |
| |
| ret = it51xxx_set_tx_rx_length(dev, tx_length, rx_length); |
| if (ret) { |
| return ret; |
| } |
| |
| if (tx_length) { |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| memcpy(data->dlm_data.tx_data, i3c_msgs[data->curr_msg.curr_idx].buf, |
| tx_length); |
| } else { |
| memcpy(data->dlm_data.tx_data, i2c_msgs[data->curr_msg.curr_idx].buf, |
| tx_length); |
| } |
| } |
| |
| sys_write8(I3CM_TARGET_ADDRESS(dynamic_addr), cfg->base + I3CM02_TARGET_ADDRESS); |
| |
| /* set cycle type register */ |
| it51xxx_set_op_type(dev, cycle_type, more_transfer, broadcast_address); |
| data->msg_state = IT51XXX_I3CM_MSG_PRIVATE_XFER; |
| |
| return 0; |
| } |
| |
| static inline int it51xxx_set_i2c_clock(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_config_controller *config_cntlr = &data->common.ctrl_config; |
| uint32_t t_high_period_ns, t_low_period_ns; |
| uint32_t t_high, t_low; |
| uint16_t t_hddat = |
| (cfg->clocks.i2c_scl_hddat > 0xFFFF) ? 0xFFFF : cfg->clocks.i2c_scl_hddat; |
| |
| /* high_period_ns = ns_per_sec / config_cntlr->scl.i2c / 2; |
| * high_period_ns = (t_high + 1) * 20.8 |
| * t_high = ((ns_per_sec / config_cntlr->scl.i2c / 2) / 20.8) - 1 |
| */ |
| t_high_period_ns = NSEC_PER_SEC / config_cntlr->scl.i2c / 2; |
| t_high = DIV_ROUND_UP(t_high_period_ns * 10, 208) - 1; |
| |
| /* t_low_period_ns = (ns_per_sec / config_cntlr->scl.i2c) - high_period_ns |
| * t_low_period_ns = (t_low + 1 + t_hddat + 1) * 20.8 |
| * t_low = (t_low_period_ns / 20.8) - t_hddat - 2 |
| */ |
| t_low_period_ns = (NSEC_PER_SEC / config_cntlr->scl.i2c) - t_high_period_ns; |
| t_low = DIV_ROUND_UP(t_low_period_ns * 10, 208) - t_hddat - 2; |
| |
| if (t_high > 0xFFFF || t_low > 0xFFFF) { |
| LOG_INST_ERR(cfg->log, "invalid t_high(0x%x) or t_low(0x%x) setting", t_high, |
| t_low); |
| } |
| |
| sys_write8(BYTE_0(t_high), cfg->base + I3CM30_I2C_THDSTA_SUSTO_LB); |
| sys_write8(BYTE_1(t_high), cfg->base + I3CM31_I2C_THDSTA_SUSTO_HB); |
| sys_write8(BYTE_0(t_hddat), cfg->base + I3CM34_I2C_THDDAT_LB); |
| sys_write8(BYTE_1(t_hddat), cfg->base + I3CM35_I2C_THDDAT_HB); |
| sys_write8(BYTE_0(t_low), cfg->base + I3CM36_I2C_TLOW_LB); |
| sys_write8(BYTE_1(t_low), cfg->base + I3CM37_I2C_TLOW_HB); |
| sys_write8(BYTE_0(t_high), cfg->base + I3CM38_I2C_THIGH_LB); |
| sys_write8(BYTE_1(t_high), cfg->base + I3CM39_I2C_THIGH_HB); |
| |
| LOG_INST_DBG(cfg->log, "i2c: t_high 0x%x, t_low 0x%x t_hddat 0x%x", t_high, t_low, t_hddat); |
| LOG_INST_DBG(cfg->log, "i2c: high period: %dns, low period: %dns", t_high_period_ns, |
| t_low_period_ns); |
| LOG_INST_INF(cfg->log, "i2c: freq: %dHz -> %dHz", config_cntlr->scl.i2c, |
| CALC_FREQUENCY(t_low, t_hddat, t_high)); |
| |
| return 0; |
| } |
| |
| static inline int it51xxx_set_i3c_clock(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_config_controller *config_cntlr = &data->common.ctrl_config; |
| uint32_t pp_freq, od_freq; |
| uint32_t odlow_ns, odhigh_ns, pplow_ns, pphigh_ns; |
| uint16_t pphigh, pplow, odhigh, odlow; |
| uint8_t pp_duty_cycle = |
| (cfg->clocks.i3c_pp_duty_cycle > 100) ? 100 : cfg->clocks.i3c_pp_duty_cycle; |
| uint8_t hddat = (cfg->clocks.i3c_scl_hddat > 63) ? 63 : cfg->clocks.i3c_scl_hddat; |
| uint8_t tcas = (cfg->clocks.i3c_scl_tcas > 0xff) ? 0xff : cfg->clocks.i3c_scl_tcas; |
| uint8_t tcbs = (cfg->clocks.i3c_scl_tcbs > 0xff) ? 0xff : cfg->clocks.i3c_scl_tcbs; |
| uint8_t tcasr = (cfg->clocks.i3c_scl_tcasr > 0xff) ? 0xff : cfg->clocks.i3c_scl_tcasr; |
| uint8_t tcbsr = (cfg->clocks.i3c_scl_tcbsr > 0xff) ? 0xff : cfg->clocks.i3c_scl_tcbsr; |
| |
| pp_freq = config_cntlr->scl.i3c; |
| od_freq = cfg->clocks.i3c_od_scl_hz; |
| if (pp_freq == 0 || od_freq == 0) { |
| LOG_INST_ERR(cfg->log, "invalid freq pp(%dHz) or od(%dHz)", pp_freq, od_freq); |
| return -EINVAL; |
| } |
| |
| /* use i3c timing setting */ |
| sys_write8(sys_read8(cfg->base + I3CM2F_I2C_CONTROL) & ~I3CM_USE_I2C_TIMING_SETTING, |
| cfg->base + I3CM2F_I2C_CONTROL); |
| |
| /* pphigh_ns = odhigh_ns = (ns_per_sec / pp_freq) * duty_cycle |
| * pplow_ns = (ns_per_sec / pp_freq) - pphigh_ns |
| * odlow_ns = (ns_per_sec / od_freq) - odhigh_ns |
| */ |
| pphigh_ns = DIV_ROUND_UP(DIV_ROUND_UP(NSEC_PER_SEC, pp_freq) * pp_duty_cycle, 100); |
| pplow_ns = DIV_ROUND_UP(NSEC_PER_SEC, pp_freq) - pphigh_ns; |
| odhigh_ns = pphigh_ns; |
| odlow_ns = DIV_ROUND_UP(NSEC_PER_SEC, od_freq) - odhigh_ns; |
| if (odlow_ns < I3C_BUS_TLOW_OD_MIN_NS) { |
| LOG_INST_ERR(cfg->log, "od low period(%dns) is out of spec", odlow_ns); |
| return -EINVAL; |
| } |
| if (pphigh_ns < I3C_BUS_THIGH_PP_MIN_NS) { |
| LOG_INST_ERR(cfg->log, "pp high period(%dns) is out of spec", pphigh_ns); |
| return -EINVAL; |
| } |
| if (pplow_ns < I3C_BUS_TLOW_PP_MIN_NS) { |
| LOG_INST_ERR(cfg->log, "pp low period(%dns) is out of spec", pplow_ns); |
| return -EINVAL; |
| } |
| |
| /* odlow_ns = (odlow + 1) * 20.8 + (hddat + 1) * 20.8 |
| * odlow = (odlow_ns / 20.8) - hddat - 2 |
| */ |
| odlow = DIV_ROUND_UP(odlow_ns * 10, 208) - hddat - 2; |
| odlow = (odlow > 0x1ff) ? 0x1ff : odlow; |
| sys_write8(BYTE_0(odlow), cfg->base + I3CM2A_TLOW_OD_LB); |
| sys_write8(BYTE_1(odlow), cfg->base + I3CM2B_TLOW_OD_HB); |
| |
| /* pphigh_ns = (pphigh + 1) * 20.8 |
| * pphigh = (pphigh_ns / 20.8) - 1 |
| * odhigh = pphigh |
| */ |
| pphigh = DIV_ROUND_UP(pphigh_ns * 10, 208) - 1; |
| pphigh = (pphigh > 0x1ff) ? 0x1ff : pphigh; |
| odhigh = pphigh; |
| sys_write8(BYTE_0(pphigh), cfg->base + I3CM28_THIGH_LB); |
| sys_write8(BYTE_1(pphigh), cfg->base + I3CM29_THIGH_HB); |
| |
| /* pplow_ns = (pplow + 1) * 20.8 + (hddat + 1) * 20.8 |
| * pplow = (pplow_ns / 20.8) - hddat - 2 |
| */ |
| pplow = DIV_ROUND_UP(pplow_ns * 10, 208) - hddat - 2; |
| pplow = (pplow > 0x1ff) ? 0x1ff : pplow; |
| sys_write8(BYTE_0(pplow), cfg->base + I3CM26_TLOW_LB); |
| sys_write8(BYTE_1(pplow), cfg->base + I3CM27_TLOW_HB); |
| |
| sys_write8(hddat, cfg->base + I3CM24_THDDAT_LB); |
| sys_write8(tcas, cfg->base + I3CM20_TCAS); |
| sys_write8(tcbs, cfg->base + I3CM21_TCBP); |
| sys_write8(tcasr, cfg->base + I3CM23_TCASR); |
| sys_write8(tcbsr, cfg->base + I3CM22_TCBSR); |
| |
| LOG_INST_DBG(cfg->log, "i3c: pphigh_ns: %dns, pplow_ns %dns", pphigh_ns, pplow_ns); |
| LOG_INST_DBG(cfg->log, "i3c: odhigh_ns: %dns, odlow_ns %dns", odhigh_ns, odlow_ns); |
| LOG_INST_DBG(cfg->log, "i3c: pphigh: %d, pplow %d, odhigh: %d, odlow %d, hddat %d", pphigh, |
| pplow, odhigh, odlow, hddat); |
| LOG_INST_INF(cfg->log, "i3c: pp_freq: %dHz -> %dHz, od_freq %dHz -> %dHz", pp_freq, |
| CALC_FREQUENCY(pplow, hddat, pphigh), od_freq, |
| CALC_FREQUENCY(odlow, hddat, odhigh)); |
| |
| return 0; |
| } |
| |
| static int it51xxx_set_frequency(const struct device *dev) |
| { |
| int ret; |
| |
| ret = it51xxx_set_i3c_clock(dev); |
| if (ret) { |
| goto out; |
| } |
| |
| ret = it51xxx_set_i2c_clock(dev); |
| if (ret) { |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int it51xxx_prepare_priv_xfer(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_msg *i3c_msgs = data->curr_msg.i3c_msgs; |
| struct i2c_msg *i2c_msgs = data->curr_msg.i2c_msgs; |
| bool more_transfer = false, send_broadcast = false, emit_stop, is_read; |
| int ret = 0; |
| uint8_t cycle_type; |
| |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| emit_stop = i3c_msgs[data->curr_msg.curr_idx].flags & I3C_MSG_STOP; |
| is_read = |
| (i3c_msgs[data->curr_msg.curr_idx].flags & I3C_MSG_RW_MASK) == I3C_MSG_READ; |
| } else { |
| emit_stop = i2c_msgs[data->curr_msg.curr_idx].flags & I2C_MSG_STOP; |
| is_read = |
| (i2c_msgs[data->curr_msg.curr_idx].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ; |
| } |
| |
| if (emit_stop) { |
| if ((data->curr_msg.curr_idx + 1) != data->curr_msg.num_msgs) { |
| LOG_INST_ERR(cfg->log, "invalid message: too much messages"); |
| return -EINVAL; |
| } |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| cycle_type = is_read ? PRIVATE_READ_TRANSFER : PRIVATE_WRITE_TRANSFER; |
| } else { |
| cycle_type = is_read ? LEGACY_I2C_READ_TRANSFER : LEGACY_I2C_WRITE_TRANSFER; |
| } |
| } else { |
| bool next_is_read; |
| bool next_is_restart; |
| |
| if ((data->curr_msg.curr_idx + 1) > data->curr_msg.num_msgs) { |
| LOG_INST_ERR(cfg->log, "invalid message: too few messages"); |
| return -EINVAL; |
| } |
| |
| if (is_read) { |
| LOG_INST_ERR(cfg->log, |
| "invalid message: multiple msgs initiated from the read flag"); |
| return -EINVAL; |
| } |
| |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| next_is_read = (i3c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I3C_MSG_RW_MASK) == I3C_MSG_READ; |
| next_is_restart = ((i3c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I3C_MSG_RESTART) == I3C_MSG_RESTART); |
| } else { |
| next_is_read = (i2c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I2C_MSG_RW_MASK) == I2C_MSG_READ; |
| next_is_restart = ((i2c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I2C_MSG_RESTART) == I2C_MSG_RESTART); |
| } |
| |
| if (!next_is_read && !next_is_restart) { |
| /* burst write */ |
| if (!it51xxx_curr_msg_is_i3c(dev)) { |
| /* legacy i2c transfer doesn't support burst write */ |
| return -ENOTSUP; |
| } |
| cycle_type = PRIVATE_WRITE_TRANSFER; |
| more_transfer = true; |
| } else if (next_is_read) { |
| /* write then read */ |
| cycle_type = it51xxx_curr_msg_is_i3c(dev) ? PRIVATE_WRITE_READ_TRANSFER |
| : LEGACY_I2C_WRITE_READ_TRANSFER; |
| } else { |
| LOG_INST_ERR(cfg->log, "invalid message"); |
| return -EINVAL; |
| } |
| } |
| |
| if (it51xxx_curr_msg_is_i3c(dev) && data->curr_msg.curr_idx == 0 && |
| !(i3c_msgs[data->curr_msg.curr_idx].flags & I3C_MSG_NBCH)) { |
| send_broadcast = true; |
| } |
| |
| ret = it51xxx_start_i3c_i2c_private_xfer(dev, cycle_type, data->curr_msg.target_addr, |
| more_transfer, send_broadcast); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int it51xxx_i3cm_i2c_api_transfer(const struct device *dev, struct i2c_msg *msgs, |
| uint8_t num_msgs, uint16_t addr) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret; |
| |
| if (!msgs) { |
| return -EINVAL; |
| } |
| |
| if (num_msgs == 0) { |
| return 0; |
| } |
| |
| for (uint8_t i = 0; i < num_msgs; i++) { |
| if (!msgs[i].buf) { |
| return -EINVAL; |
| } |
| if (msgs[i].flags & I2C_MSG_ADDR_10_BITS) { |
| LOG_INST_ERR(cfg->log, "unsupported i2c extended address"); |
| return -ENOTSUP; |
| } |
| } |
| |
| irq_disable(cfg->irq_num); |
| if (!bus_is_idle(dev)) { |
| irq_enable(cfg->irq_num); |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| it51xxx_enable_standby_state(dev, false); |
| |
| it51xxx_curr_msg_init(dev, NULL, msgs, num_msgs, addr); |
| ret = it51xxx_prepare_priv_xfer(dev); |
| if (ret) { |
| irq_enable(cfg->irq_num); |
| goto out; |
| } |
| |
| /* start transfer */ |
| sys_write8(START_TRANSFER, cfg->base + I3CM01_STATUS); |
| irq_enable(cfg->irq_num); |
| |
| ret = it51xxx_wait_to_complete(dev); |
| |
| out: |
| data->curr_msg.curr_idx = 0; |
| it51xxx_enable_standby_state(dev, true); |
| k_mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int it51xxx_i3cm_configure(const struct device *dev, enum i3c_config_type type, void *config) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_config_controller *cntlr_cfg = config; |
| int ret; |
| |
| if (type != I3C_CONFIG_CONTROLLER) { |
| LOG_INST_ERR(cfg->log, "support controller mode only"); |
| return -ENOTSUP; |
| } |
| |
| if (cntlr_cfg->is_secondary || cntlr_cfg->scl.i3c == 0U || cntlr_cfg->scl.i2c == 0U) { |
| return -EINVAL; |
| } |
| |
| (void)memcpy(&data->common.ctrl_config, cntlr_cfg, sizeof(*cntlr_cfg)); |
| k_mutex_lock(&data->lock, K_FOREVER); |
| ret = it51xxx_set_frequency(dev); |
| k_mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int it51xxx_i3cm_config_get(const struct device *dev, enum i3c_config_type type, |
| void *config) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| if (type != I3C_CONFIG_CONTROLLER) { |
| return -ENOTSUP; |
| } |
| |
| if (!config) { |
| return -EINVAL; |
| } |
| |
| (void)memcpy(config, &data->common.ctrl_config, sizeof(data->common.ctrl_config)); |
| |
| return 0; |
| } |
| |
| static int it51xxx_i3cm_do_daa(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret = 0; |
| |
| LOG_INST_DBG(cfg->log, "start daa"); |
| |
| irq_disable(cfg->irq_num); |
| if (!bus_is_idle(dev)) { |
| irq_enable(cfg->irq_num); |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| data->msg_state = IT51XXX_I3CM_MSG_DAA; |
| |
| it51xxx_enable_standby_state(dev, false); |
| it51xxx_set_op_type(dev, DAA_TRANSFER, false, true); |
| sys_write8(START_TRANSFER, cfg->base + I3CM01_STATUS); |
| irq_enable(cfg->irq_num); |
| |
| ret = it51xxx_wait_to_complete(dev); |
| |
| it51xxx_enable_standby_state(dev, true); |
| k_mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int it51xxx_broadcast_ccc_xfer(const struct device *dev, struct i3c_ccc_payload *payload) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret; |
| |
| irq_disable(cfg->irq_num); |
| if (!bus_is_idle(dev)) { |
| irq_enable(cfg->irq_num); |
| return -EBUSY; |
| } |
| |
| ret = it51xxx_set_tx_rx_length(dev, payload->ccc.data_len, 0); |
| if (ret) { |
| irq_enable(cfg->irq_num); |
| return ret; |
| } |
| |
| if (payload->ccc.data_len > 0) { |
| memcpy(data->dlm_data.tx_data, payload->ccc.data, payload->ccc.data_len); |
| } |
| |
| data->ccc_msgs.payload = payload; |
| data->msg_state = IT51XXX_I3CM_MSG_BROADCAST_CCC; |
| it51xxx_set_op_type(dev, BROADCAST_CCC_WRITE_TRANSFER, false, true); |
| sys_write8(START_TRANSFER, cfg->base + I3CM01_STATUS); |
| irq_enable(cfg->irq_num); |
| |
| return it51xxx_wait_to_complete(dev); |
| } |
| |
| static void it51xxx_direct_ccc_xfer_end(const struct device *dev) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_ccc_target_payload *tgt_payload = data->ccc_msgs.payload->targets.payloads; |
| size_t target_idx = data->ccc_msgs.target_idx; |
| bool is_read = tgt_payload[target_idx].rnw == 1U; |
| size_t data_count; |
| |
| if (is_read) { |
| data_count = it51xxx_get_received_data_count(dev); |
| memcpy(tgt_payload[target_idx].data, data->dlm_data.rx_data, |
| MIN(tgt_payload[target_idx].data_len, data_count)); |
| LOG_HEXDUMP_DBG(tgt_payload[target_idx].data, tgt_payload[target_idx].data_len, |
| "direct ccc rx:"); |
| } |
| tgt_payload[target_idx].num_xfer = is_read ? data_count : tgt_payload[target_idx].data_len; |
| } |
| |
| static int it51xxx_start_direct_ccc_xfer(const struct device *dev, struct i3c_ccc_payload *payload) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_ccc_target_payload *tgt_payload = &payload->targets.payloads[0]; |
| bool is_read = tgt_payload->rnw == 1U; |
| bool more_transfer; |
| uint8_t cycle_type; |
| int ret; |
| |
| irq_disable(cfg->irq_num); |
| if (!bus_is_idle(dev)) { |
| irq_enable(cfg->irq_num); |
| return -EBUSY; |
| } |
| |
| if (is_read) { |
| ret = it51xxx_set_tx_rx_length(dev, 0, tgt_payload->data_len); |
| if (ret) { |
| irq_enable(cfg->irq_num); |
| return ret; |
| } |
| cycle_type = DIRECT_CCC_READ_TRANSFER; |
| } else { |
| ret = it51xxx_set_tx_rx_length(dev, tgt_payload->data_len, 0); |
| if (ret) { |
| irq_enable(cfg->irq_num); |
| return ret; |
| } |
| |
| memcpy(data->dlm_data.tx_data, tgt_payload->data, tgt_payload->data_len); |
| cycle_type = DIRECT_CCC_WRITE_TRANSFER; |
| } |
| |
| data->ccc_msgs.payload = payload; |
| data->msg_state = IT51XXX_I3CM_MSG_DIRECT_CCC; |
| more_transfer = (payload->targets.num_targets > 1) ? true : false; |
| it51xxx_set_op_type(dev, cycle_type, more_transfer, true); |
| sys_write8(I3CM_TARGET_ADDRESS(tgt_payload->addr), cfg->base + I3CM02_TARGET_ADDRESS); |
| sys_write8(START_TRANSFER, cfg->base + I3CM01_STATUS); |
| irq_enable(cfg->irq_num); |
| |
| return it51xxx_wait_to_complete(dev); |
| } |
| |
| static int it51xxx_i3cm_do_ccc(const struct device *dev, struct i3c_ccc_payload *payload) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret = 0; |
| |
| if (!payload) { |
| return -EINVAL; |
| } |
| |
| LOG_INST_DBG(cfg->log, "send %s ccc(0x%x)", |
| i3c_ccc_is_payload_broadcast(payload) ? "broadcast" : "direct", |
| payload->ccc.id); |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| /* disable ccc defining byte */ |
| sys_write8(sys_read8(cfg->base + I3CM15_CONTROL_2) & ~I3CM_CCC_WITH_DEFINING_BYTE, |
| cfg->base + I3CM15_CONTROL_2); |
| |
| if (!i3c_ccc_is_payload_broadcast(payload)) { |
| if (payload->ccc.data_len > 1) { |
| LOG_INST_ERR(cfg->log, "only support 1 ccc defining byte"); |
| ret = -ENOTSUP; |
| goto out; |
| } |
| if (payload->ccc.data_len > 0 && payload->ccc.data == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| if (payload->targets.payloads == NULL || payload->targets.num_targets == 0) { |
| ret = -EINVAL; |
| goto out; |
| } |
| if (payload->ccc.data_len) { |
| /* set ccc defining byte */ |
| sys_write8(sys_read8(cfg->base + I3CM15_CONTROL_2) | |
| I3CM_CCC_WITH_DEFINING_BYTE, |
| cfg->base + I3CM15_CONTROL_2); |
| sys_write8(payload->ccc.data[0], cfg->base + I3CM16_CCC_DEFINING_BYTE); |
| } |
| } else { |
| if (payload->ccc.data_len > 0 && payload->ccc.data == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| it51xxx_enable_standby_state(dev, false); |
| |
| sys_write8(payload->ccc.id, cfg->base + I3CM03_COMMON_COMMAND_CODE); |
| |
| if (i3c_ccc_is_payload_broadcast(payload)) { |
| ret = it51xxx_broadcast_ccc_xfer(dev, payload); |
| } else { |
| ret = it51xxx_start_direct_ccc_xfer(dev, payload); |
| } |
| |
| it51xxx_enable_standby_state(dev, true); |
| |
| out: |
| k_mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static struct i3c_device_desc *it51xxx_i3cm_device_find(const struct device *dev, |
| const struct i3c_device_id *id) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| |
| return i3c_dev_list_find(&cfg->common.dev_list, id); |
| } |
| |
| static int it51xxx_i3cm_transfer(const struct device *dev, struct i3c_device_desc *target, |
| struct i3c_msg *msgs, uint8_t num_msgs) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| int ret; |
| |
| if (!msgs || target->dynamic_addr == 0U) { |
| return -EINVAL; |
| } |
| |
| if (num_msgs == 0) { |
| return 0; |
| } |
| |
| for (uint8_t i = 0; i < num_msgs; i++) { |
| if (!msgs[i].buf) { |
| return -EINVAL; |
| } |
| if ((msgs[i].flags & I3C_MSG_HDR) && (msgs[i].hdr_mode != 0)) { |
| LOG_INST_ERR(cfg->log, "unsupported hdr mode"); |
| return -ENOTSUP; |
| } |
| } |
| |
| irq_disable(cfg->irq_num); |
| if (!bus_is_idle(dev)) { |
| irq_enable(cfg->irq_num); |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| it51xxx_enable_standby_state(dev, false); |
| |
| it51xxx_curr_msg_init(dev, msgs, NULL, num_msgs, target->dynamic_addr); |
| ret = it51xxx_prepare_priv_xfer(dev); |
| if (ret) { |
| irq_enable(cfg->irq_num); |
| goto out; |
| } |
| |
| /* start transfer */ |
| sys_write8(START_TRANSFER, cfg->base + I3CM01_STATUS); |
| irq_enable(cfg->irq_num); |
| |
| ret = it51xxx_wait_to_complete(dev); |
| |
| out: |
| it51xxx_enable_standby_state(dev, true); |
| data->curr_msg.curr_idx = 0; |
| k_mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static inline void it51xxx_accept_ibi(const struct device *dev, bool accept) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| |
| if (accept) { |
| sys_write8(sys_read8(cfg->base + I3CM10_CONTROL) & ~I3CM_REFUSE_IBI, |
| cfg->base + I3CM10_CONTROL); |
| } else { |
| sys_write8(sys_read8(cfg->base + I3CM10_CONTROL) | I3CM_REFUSE_IBI, |
| cfg->base + I3CM10_CONTROL); |
| } |
| } |
| |
| #ifdef CONFIG_I3C_USE_IBI |
| static int it51xxx_i3cm_ibi_hj_response(const struct device *dev, bool ack) |
| { |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| data->ibi_hj_response = ack; |
| |
| return 0; |
| } |
| |
| static int it51xxx_i3cm_ibi_enable(const struct device *dev, struct i3c_device_desc *target) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_ccc_events i3c_events; |
| int ret; |
| uint8_t idx; |
| |
| if (!i3c_ibi_has_payload(target)) { |
| LOG_INST_ERR(cfg->log, "i3cm only supports ibi with payload"); |
| return -ENOTSUP; |
| } |
| |
| if (!i3c_device_is_ibi_capable(target)) { |
| return -EINVAL; |
| } |
| |
| if (data->ibi.num_addr >= ARRAY_SIZE(data->ibi.addr)) { |
| LOG_INST_ERR(cfg->log, "no more free space in the ibi list"); |
| return -ENOMEM; |
| } |
| |
| for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { |
| if (data->ibi.addr[idx] == target->dynamic_addr) { |
| LOG_INST_ERR(cfg->log, "selected target is already in the ibi list"); |
| return -EINVAL; |
| } |
| } |
| |
| if (data->ibi.num_addr > 0) { |
| 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_INST_ERR(cfg->log, "cannot support more ibis"); |
| return -ENOTSUP; |
| } |
| } else { |
| idx = 0; |
| } |
| |
| LOG_INST_DBG(cfg->log, "ibi enabling for 0x%x (bcr 0x%x)", target->dynamic_addr, |
| target->bcr); |
| |
| /* enable target ibi event by enec command */ |
| i3c_events.events = I3C_CCC_EVT_INTR; |
| ret = i3c_ccc_do_events_set(target, true, &i3c_events); |
| if (ret != 0) { |
| LOG_INST_ERR(cfg->log, "failed to send ibi enec for 0x%x(%d)", target->dynamic_addr, |
| ret); |
| return ret; |
| } |
| |
| data->ibi.addr[idx] = target->dynamic_addr; |
| data->ibi.num_addr += 1U; |
| |
| if (data->ibi.num_addr == 1U) { |
| it51xxx_enable_standby_state(dev, false); |
| } |
| |
| return 0; |
| } |
| |
| static int it51xxx_i3cm_ibi_disable(const struct device *dev, struct i3c_device_desc *target) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_ccc_events i3c_events; |
| int ret; |
| uint8_t idx; |
| |
| if (!i3c_device_is_ibi_capable(target)) { |
| return -EINVAL; |
| } |
| |
| for (idx = 0; idx < ARRAY_SIZE(data->ibi.addr); idx++) { |
| if (target->dynamic_addr == data->ibi.addr[idx]) { |
| break; |
| } |
| } |
| |
| if (idx == ARRAY_SIZE(data->ibi.addr)) { |
| LOG_INST_ERR(cfg->log, "selected target is not in ibi list"); |
| return -ENODEV; |
| } |
| |
| data->ibi.addr[idx] = 0U; |
| data->ibi.num_addr -= 1U; |
| |
| if (data->ibi.num_addr == 0U) { |
| it51xxx_enable_standby_state(dev, true); |
| } |
| |
| LOG_INST_DBG(cfg->log, "ibi disabling for 0x%x (bcr 0x%x)", target->dynamic_addr, |
| target->bcr); |
| |
| /* disable target ibi event by disec command */ |
| i3c_events.events = I3C_CCC_EVT_INTR; |
| ret = i3c_ccc_do_events_set(target, false, &i3c_events); |
| if (ret != 0) { |
| LOG_INST_ERR(cfg->log, "failed to send ibi disec for 0x%x(%d)", |
| target->dynamic_addr, ret); |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_I3C_USE_IBI */ |
| |
| static enum i3c_bus_mode i3c_bus_mode(const struct i3c_dev_list *dev_list) |
| { |
| enum i3c_bus_mode mode = I3C_BUS_MODE_PURE; |
| |
| for (int i = 0; i < dev_list->num_i2c; i++) { |
| switch (I3C_LVR_I2C_DEV_IDX(dev_list->i2c[i].lvr)) { |
| case I3C_LVR_I2C_DEV_IDX_0: |
| if (mode < I3C_BUS_MODE_MIXED_FAST) { |
| mode = I3C_BUS_MODE_MIXED_FAST; |
| } |
| break; |
| case I3C_LVR_I2C_DEV_IDX_1: |
| if (mode < I3C_BUS_MODE_MIXED_LIMITED) { |
| mode = I3C_BUS_MODE_MIXED_LIMITED; |
| } |
| break; |
| case I3C_LVR_I2C_DEV_IDX_2: |
| if (mode < I3C_BUS_MODE_MIXED_SLOW) { |
| mode = I3C_BUS_MODE_MIXED_SLOW; |
| } |
| break; |
| default: |
| mode = I3C_BUS_MODE_INVALID; |
| break; |
| } |
| } |
| |
| return mode; |
| } |
| |
| static int it51xxx_i3cm_init(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_config_controller *ctrl_config = &data->common.ctrl_config; |
| 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; |
| } |
| |
| ctrl_config->is_secondary = false; |
| ctrl_config->supported_hdr = 0x0; |
| |
| k_sem_init(&data->msg_sem, 0, 1); |
| k_mutex_init(&data->lock); |
| |
| if (i3c_bus_mode(&cfg->common.dev_list) != I3C_BUS_MODE_PURE) { |
| LOG_INST_ERR(cfg->log, "only support pure mode currently"); |
| return -ENOTSUP; |
| } |
| |
| ret = i3c_addr_slots_init(dev); |
| if (ret != 0) { |
| LOG_INST_ERR(cfg->log, "failed to init slots, ret %d", ret); |
| return ret; |
| } |
| |
| /* clear status, enable the interrupt and refuse ibi bits */ |
| sys_write8(sys_read8(cfg->base + I3CM01_STATUS) & ~START_TRANSFER, |
| cfg->base + I3CM01_STATUS); |
| sys_write8(sys_read8(cfg->base + I3CM10_CONTROL) | |
| (I3CM_REFUSE_IBI | I3CM_INTERRUPT_ENABLE), |
| cfg->base + I3CM10_CONTROL); |
| cfg->irq_config_func(dev); |
| |
| reg_val = sys_read8(cfg->base + I3CM50_CONTROL_3); |
| reg_val &= ~I3CM_DLM_SIZE_MASK; |
| switch (CONFIG_I3CM_IT51XXX_DLM_SIZE) { |
| case 256: |
| reg_val |= FIELD_PREP(I3CM_DLM_SIZE_MASK, 0); |
| break; |
| case 512: |
| reg_val |= FIELD_PREP(I3CM_DLM_SIZE_MASK, 1); |
| break; |
| case 1024: |
| reg_val |= FIELD_PREP(I3CM_DLM_SIZE_MASK, 2); |
| break; |
| default: |
| LOG_INST_ERR(cfg->log, "invalid dlm size(%d)", CONFIG_I3CM_IT51XXX_DLM_SIZE); |
| return -EINVAL; |
| }; |
| |
| /* set i3cm channel selection */ |
| reg_val &= ~I3CM_CHANNEL_SELECT_MASK; |
| LOG_INST_DBG(cfg->log, "channel %d is selected", cfg->io_channel); |
| reg_val |= FIELD_PREP(I3CM_CHANNEL_SELECT_MASK, cfg->io_channel); |
| |
| /* select 4k pull-up resistor and enable i3c engine*/ |
| reg_val |= (I3CM_PULL_UP_RESISTOR | I3CM_ENABLE); |
| sys_write8(reg_val, cfg->base + I3CM50_CONTROL_3); |
| |
| LOG_INST_DBG(cfg->log, "dlm base address 0x%x", (uint32_t)&data->dlm_data); |
| sys_write8(FIELD_GET(GENMASK(17, 16), (uint32_t)&data->dlm_data), |
| cfg->base + I3CM53_DLM_BASE_ADDRESS_HB); |
| sys_write8(BYTE_1((uint32_t)&data->dlm_data), cfg->base + I3CM52_DLM_BASE_ADDRESS_LB); |
| |
| ret = it51xxx_set_frequency(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| data->is_initialized = true; |
| |
| #ifdef CONFIG_I3C_USE_IBI |
| data->ibi_hj_response = true; |
| #endif |
| |
| if (cfg->common.dev_list.num_i3c > 0) { |
| ret = i3c_bus_init(dev, &cfg->common.dev_list); |
| if (ret != 0) { |
| /* Perhaps the target device is offline. Avoid returning |
| * an error code to allow the application layer to |
| * reinitialize by sending ccc. |
| */ |
| LOG_INST_ERR(cfg->log, "failed to init i3c bus, ret %d", ret); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int it51xxx_daa_next_xfer(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_device_desc *target; |
| int ret; |
| uint64_t pid; |
| uint32_t part_no; |
| uint16_t vendor_id; |
| uint8_t dyn_addr = 0; |
| size_t rx_count; |
| |
| rx_count = it51xxx_get_received_data_count(dev); |
| if (rx_count != 8) { |
| LOG_INST_ERR(cfg->log, "daa: rx count (%d) not as expected", rx_count); |
| return -EINVAL; |
| } |
| |
| LOG_HEXDUMP_DBG(data->dlm_data.rx_data, rx_count, "6pid/1bcr/1dcr:"); |
| vendor_id = (((uint16_t)data->dlm_data.rx_data[0] << 8U) | |
| (uint16_t)data->dlm_data.rx_data[1]) & |
| 0xFFFEU; |
| part_no = (uint32_t)data->dlm_data.rx_data[2] << 24U | |
| (uint32_t)data->dlm_data.rx_data[3] << 16U | |
| (uint32_t)data->dlm_data.rx_data[4] << 8U | (uint32_t)data->dlm_data.rx_data[5]; |
| pid = (uint64_t)vendor_id << 32U | (uint64_t)part_no; |
| |
| /* find the device in the device list */ |
| ret = i3c_dev_list_daa_addr_helper(&data->common.attached_dev.addr_slots, |
| &cfg->common.dev_list, pid, false, false, &target, |
| &dyn_addr); |
| if (ret != 0) { |
| LOG_INST_ERR(cfg->log, "no dynamic address could be assigned to target"); |
| return -EINVAL; |
| } |
| |
| sys_write8(I3CM_TARGET_ADDRESS(dyn_addr), cfg->base + I3CM02_TARGET_ADDRESS); |
| |
| if (target != NULL) { |
| target->dynamic_addr = dyn_addr; |
| target->bcr = data->dlm_data.rx_data[6]; |
| target->dcr = data->dlm_data.rx_data[7]; |
| } else { |
| LOG_INST_INF( |
| cfg->log, |
| "pid 0x%04x%08x is not in registered device list, given dynamic address " |
| "0x%x", |
| vendor_id, part_no, dyn_addr); |
| } |
| |
| /* mark the address as used */ |
| i3c_addr_slots_mark_i3c(&data->common.attached_dev.addr_slots, dyn_addr); |
| |
| /* mark the static address as free */ |
| if ((target != NULL) && (target->static_addr != 0) && (dyn_addr != target->static_addr)) { |
| i3c_addr_slots_mark_free(&data->common.attached_dev.addr_slots, |
| target->static_addr); |
| } |
| |
| return 0; |
| } |
| |
| static int it51xxx_direct_ccc_next_xfer(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_ccc_target_payload *tgt_payload = data->ccc_msgs.payload->targets.payloads; |
| uint8_t cycle_type; |
| bool more_transfer; |
| bool is_read = tgt_payload[data->ccc_msgs.target_idx].rnw == 1U; |
| int ret; |
| |
| it51xxx_direct_ccc_xfer_end(dev); |
| |
| /* start next transfer */ |
| data->ccc_msgs.target_idx++; |
| if (is_read) { |
| ret = it51xxx_set_tx_rx_length(dev, 0, |
| tgt_payload[data->ccc_msgs.target_idx].data_len); |
| if (ret) { |
| return ret; |
| } |
| cycle_type = PRIVATE_READ_TRANSFER; |
| } else { |
| ret = it51xxx_set_tx_rx_length(dev, tgt_payload[data->ccc_msgs.target_idx].data_len, |
| 0); |
| if (ret) { |
| return ret; |
| } |
| memcpy(data->dlm_data.tx_data, tgt_payload[data->ccc_msgs.target_idx].data, |
| tgt_payload[data->ccc_msgs.target_idx].data_len); |
| cycle_type = PRIVATE_WRITE_TRANSFER; |
| } |
| more_transfer = |
| (data->ccc_msgs.target_idx == data->ccc_msgs.payload->targets.num_targets - 1) |
| ? false |
| : true; |
| it51xxx_set_op_type(dev, cycle_type, more_transfer, false); |
| sys_write8(I3CM_TARGET_ADDRESS(tgt_payload[data->ccc_msgs.target_idx].addr), |
| cfg->base + I3CM02_TARGET_ADDRESS); |
| |
| return 0; |
| } |
| |
| static int it51xxx_private_next_xfer(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_msg *i3c_msgs = data->curr_msg.i3c_msgs; |
| struct i2c_msg *i2c_msgs = data->curr_msg.i2c_msgs; |
| bool is_write, next_is_write, next_is_restart; |
| int ret; |
| |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| is_write = ((i3c_msgs[data->curr_msg.curr_idx].flags & I3C_MSG_RW_MASK) == |
| I3C_MSG_WRITE); |
| next_is_write = ((i3c_msgs[data->curr_msg.curr_idx + 1].flags & I3C_MSG_RW_MASK) == |
| I3C_MSG_WRITE); |
| next_is_restart = ((i3c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I3C_MSG_RESTART) == I3C_MSG_RESTART); |
| } else { |
| is_write = ((i2c_msgs[data->curr_msg.curr_idx].flags & I2C_MSG_RW_MASK) == |
| I2C_MSG_WRITE); |
| next_is_write = ((i2c_msgs[data->curr_msg.curr_idx + 1].flags & I2C_MSG_RW_MASK) == |
| I2C_MSG_WRITE); |
| next_is_restart = ((i2c_msgs[data->curr_msg.curr_idx + 1].flags & |
| I2C_MSG_RESTART) == I2C_MSG_RESTART); |
| } |
| |
| if (is_write && next_is_write && !next_is_restart) { |
| data->curr_msg.curr_idx++; |
| } else { |
| LOG_INST_ERR(cfg->log, "unknown next private xfer message"); |
| return -EINVAL; |
| } |
| |
| /* prepare the next transfer */ |
| ret = it51xxx_prepare_priv_xfer(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void it51xxx_private_xfer_end(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| struct i3c_msg *i3c_msgs = data->curr_msg.i3c_msgs; |
| struct i2c_msg *i2c_msgs = data->curr_msg.i2c_msgs; |
| size_t data_count; |
| uint8_t curr_msg_idx = data->curr_msg.curr_idx; |
| |
| if (it51xxx_curr_msg_is_i3c(dev)) { |
| if ((i3c_msgs[curr_msg_idx].flags & I3C_MSG_RW_MASK) == I3C_MSG_WRITE) { |
| i3c_msgs[curr_msg_idx].num_xfer = i3c_msgs[curr_msg_idx].len; |
| } |
| |
| if ((i3c_msgs[curr_msg_idx].flags & I3C_MSG_RW_MASK) == I3C_MSG_READ) { |
| data_count = it51xxx_get_received_data_count(dev); |
| i3c_msgs[curr_msg_idx].num_xfer = data_count; |
| memcpy(i3c_msgs[curr_msg_idx].buf, data->dlm_data.rx_data, |
| MIN(i3c_msgs[curr_msg_idx].len, data_count)); |
| LOG_INST_DBG(cfg->log, "i3c: private rx %d bytes", data_count); |
| LOG_HEXDUMP_DBG(i3c_msgs[curr_msg_idx].buf, i3c_msgs[curr_msg_idx].len, |
| "i3c: private xfer rx:"); |
| } |
| |
| if (curr_msg_idx != data->curr_msg.num_msgs - 1) { |
| if ((i3c_msgs[curr_msg_idx].flags & I3C_MSG_RW_MASK) == I3C_MSG_WRITE && |
| i3c_msgs[curr_msg_idx + 1].flags & I3C_MSG_READ) { |
| data_count = it51xxx_get_received_data_count(dev); |
| i3c_msgs[curr_msg_idx + 1].num_xfer = data_count; |
| memcpy(i3c_msgs[curr_msg_idx + 1].buf, data->dlm_data.rx_data, |
| MIN(i3c_msgs[curr_msg_idx + 1].len, data_count)); |
| LOG_INST_DBG(cfg->log, "i3c: private tx-then-rx %d bytes", |
| data_count); |
| LOG_HEXDUMP_DBG(i3c_msgs[curr_msg_idx + 1].buf, |
| i3c_msgs[curr_msg_idx + 1].len, |
| "i3c: private xfer tx-then-rx:"); |
| } |
| } |
| } else { |
| if ((i2c_msgs[curr_msg_idx].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { |
| data_count = it51xxx_get_received_data_count(dev); |
| memcpy(i2c_msgs[curr_msg_idx].buf, data->dlm_data.rx_data, |
| MIN(i2c_msgs[curr_msg_idx].len, data_count)); |
| LOG_INST_DBG(cfg->log, "i2c: private rx %d bytes", data_count); |
| LOG_HEXDUMP_DBG(i2c_msgs[curr_msg_idx].buf, i2c_msgs[curr_msg_idx].len, |
| "i2c: private xfer rx:"); |
| } |
| |
| if (curr_msg_idx != data->curr_msg.num_msgs - 1) { |
| if ((i2c_msgs[curr_msg_idx].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE && |
| i2c_msgs[curr_msg_idx + 1].flags & I2C_MSG_READ) { |
| data_count = it51xxx_get_received_data_count(dev); |
| memcpy(i2c_msgs[curr_msg_idx + 1].buf, data->dlm_data.rx_data, |
| MIN(i2c_msgs[curr_msg_idx + 1].len, data_count)); |
| LOG_INST_DBG(cfg->log, "i2c: private tx-then-rx %d bytes", |
| data_count); |
| LOG_HEXDUMP_DBG(i2c_msgs[curr_msg_idx + 1].buf, |
| i2c_msgs[curr_msg_idx + 1].len, |
| "i2c: private xfer tx-then-rx:"); |
| } |
| } |
| } |
| } |
| |
| #ifdef CONFIG_I3C_USE_IBI |
| static void it51xxx_process_ibi_payload(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| size_t payload_sz = 0; |
| struct i3c_device_desc *target = i3c_dev_list_i3c_addr_find(dev, data->ibi_target_addr); |
| |
| if (i3c_ibi_has_payload(target)) { |
| payload_sz = it51xxx_get_received_data_count(dev); |
| if (payload_sz == 0) { |
| /* wrong ibi transaction due to missing payload. |
| * a 100us timeout on the targe side may cause this |
| * situation. |
| */ |
| return; |
| } |
| |
| if (payload_sz > CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE) { |
| LOG_INST_WRN(cfg->log, "ibi payloads(%d) is too much", payload_sz); |
| } |
| } |
| |
| if (i3c_ibi_work_enqueue_target_irq(target, data->dlm_data.rx_data, |
| MIN(payload_sz, CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE)) != |
| 0) { |
| LOG_INST_ERR(cfg->log, "failed to enqueue tir work"); |
| } |
| } |
| #endif /* CONFIG_I3C_USE_IBI */ |
| |
| static inline void it51xxx_check_error(const struct device *dev, const uint8_t int_status) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| |
| if (int_status & PARITY_ERROR) { |
| LOG_INST_ERR(cfg->log, "isr: transaction(%d) parity error", data->msg_state); |
| data->msg_state = IT51XXX_I3CM_MSG_ERROR; |
| sys_write8(PARITY_ERROR, cfg->base + I3CM01_STATUS); |
| } |
| |
| if (int_status & CRC5_ERROR) { |
| LOG_INST_ERR(cfg->log, "isr: transaction(%d) crc5 error", data->msg_state); |
| data->msg_state = IT51XXX_I3CM_MSG_ERROR; |
| sys_write8(CRC5_ERROR, cfg->base + I3CM01_STATUS); |
| } |
| } |
| |
| static void it51xxx_i3cm_isr(const struct device *dev) |
| { |
| const struct it51xxx_i3cm_config *cfg = dev->config; |
| struct it51xxx_i3cm_data *data = dev->data; |
| uint8_t int_status; |
| |
| int_status = sys_read8(cfg->base + I3CM01_STATUS); |
| int_status &= ~START_TRANSFER; |
| |
| if (!data->is_initialized) { |
| LOG_INST_DBG(cfg->log, |
| "i3cm interrupt(0x%x) occurs before initialization was complete", |
| int_status); |
| } |
| |
| it51xxx_check_error(dev, int_status); |
| |
| if (int_status & IBI_INTERRUPT) { |
| LOG_INST_DBG(cfg->log, "isr: ibi interrupt is detected"); |
| |
| data->msg_state = bus_is_idle(dev) ? IT51XXX_I3CM_MSG_IBI : IT51XXX_I3CM_MSG_ABORT; |
| #ifdef CONFIG_I3C_USE_IBI |
| uint8_t ibi_value, ibi_address; |
| |
| ibi_value = sys_read8(cfg->base + I3CM0F_IBI_ADDRESS); |
| ibi_address = FIELD_GET(I3CM_IBI_ADDR_MASK, ibi_value); |
| if (ibi_value & I3CM_IBI_RNW) { |
| struct i3c_device_desc *target = NULL; |
| |
| target = i3c_dev_list_i3c_addr_find(dev, ibi_address); |
| if (target != NULL) { |
| data->ibi_target_addr = ibi_address; |
| if (i3c_ibi_has_payload(target)) { |
| it51xxx_set_tx_rx_length(dev, 0, |
| CONFIG_I3C_IBI_MAX_PAYLOAD_SIZE); |
| it51xxx_set_op_type(dev, IBI_READ_TRANSFER, false, true); |
| } |
| it51xxx_accept_ibi(dev, true); |
| } else { |
| it51xxx_accept_ibi(dev, false); |
| } |
| sys_write8(IBI_INTERRUPT, cfg->base + I3CM01_STATUS); |
| } else if (ibi_address == I3C_IBI_HJ_ADDR) { |
| it51xxx_accept_ibi(dev, data->ibi_hj_response); |
| sys_write8(IBI_INTERRUPT, cfg->base + I3CM01_STATUS); |
| if (data->ibi_hj_response) { |
| if (i3c_ibi_work_enqueue_hotjoin(dev) != 0) { |
| LOG_INST_ERR(cfg->log, "failed to enqueue hot-join work"); |
| } |
| } |
| } else { |
| it51xxx_accept_ibi(dev, false); |
| sys_write8(IBI_INTERRUPT, cfg->base + I3CM01_STATUS); |
| LOG_INST_ERR(cfg->log, "unsupported controller role request"); |
| } |
| #else |
| LOG_INST_ERR(cfg->log, "isr: Kconfig I3C_USE_IBI is disabled"); |
| it51xxx_accept_ibi(dev, false); |
| sys_write8(IBI_INTERRUPT, cfg->base + I3CM01_STATUS); |
| #endif /* CONFIG_I3C_USE_IBI */ |
| } |
| |
| if (int_status & TRANSFER_END) { |
| LOG_INST_DBG(cfg->log, "isr: end transfer is detected"); |
| /* clear tx and rx length */ |
| it51xxx_set_tx_rx_length(dev, 0, 0); |
| if (int_status & TARGET_NACK) { |
| LOG_INST_DBG(cfg->log, "isr: target nack is detected"); |
| if (data->msg_state == IT51XXX_I3CM_MSG_DAA) { |
| LOG_INST_DBG(cfg->log, "isr: no target should be assigned address"); |
| } else { |
| LOG_INST_ERR(cfg->log, "isr: no target responses"); |
| data->msg_state = IT51XXX_I3CM_MSG_ERROR; |
| } |
| } |
| |
| switch (data->msg_state) { |
| case IT51XXX_I3CM_MSG_ABORT: |
| LOG_INST_INF(cfg->log, "isr: transfer was aborted due to ibi transaction"); |
| data->transfer_is_aborted = true; |
| __fallthrough; |
| case IT51XXX_I3CM_MSG_IBI: |
| #ifdef CONFIG_I3C_USE_IBI |
| if (data->ibi_target_addr) { |
| it51xxx_process_ibi_payload(dev); |
| data->ibi_target_addr = 0x0; |
| } |
| #endif /* CONFIG_I3C_USE_IBI */ |
| break; |
| case IT51XXX_I3CM_MSG_BROADCAST_CCC: |
| if (data->ccc_msgs.payload->ccc.data_len > 0) { |
| data->ccc_msgs.payload->ccc.num_xfer = |
| data->ccc_msgs.payload->ccc.data_len; |
| } |
| break; |
| case IT51XXX_I3CM_MSG_PRIVATE_XFER: |
| it51xxx_private_xfer_end(dev); |
| break; |
| case IT51XXX_I3CM_MSG_DIRECT_CCC: |
| data->ccc_msgs.payload->ccc.num_xfer = data->ccc_msgs.payload->ccc.data_len; |
| it51xxx_direct_ccc_xfer_end(dev); |
| data->ccc_msgs.target_idx = 0; |
| break; |
| case IT51XXX_I3CM_MSG_ERROR: |
| LOG_INST_ERR(cfg->log, "isr: message status error"); |
| data->error_is_detected = true; |
| break; |
| case IT51XXX_I3CM_MSG_DAA: |
| LOG_INST_DBG(cfg->log, "isr: daa finished"); |
| break; |
| case IT51XXX_I3CM_MSG_IDLE: |
| LOG_INST_WRN(cfg->log, "isr: end transfer occurs but bus is in idle"); |
| break; |
| default: |
| LOG_INST_ERR(cfg->log, "isr: unknown message status(%d)", data->msg_state); |
| break; |
| } |
| |
| if (data->msg_state != IT51XXX_I3CM_MSG_IBI) { |
| k_sem_give(&data->msg_sem); |
| } |
| |
| data->msg_state = IT51XXX_I3CM_MSG_IDLE; |
| sys_write8(TARGET_NACK | TRANSFER_END, cfg->base + I3CM01_STATUS); |
| } |
| |
| if (int_status & NEXT_TRANSFER) { |
| int ret = 0; |
| |
| LOG_INST_DBG(cfg->log, "isr: next transfer is detected"); |
| switch (data->msg_state) { |
| case IT51XXX_I3CM_MSG_DAA: |
| ret = it51xxx_daa_next_xfer(dev); |
| break; |
| case IT51XXX_I3CM_MSG_DIRECT_CCC: |
| ret = it51xxx_direct_ccc_next_xfer(dev); |
| break; |
| case IT51XXX_I3CM_MSG_PRIVATE_XFER: |
| ret = it51xxx_private_next_xfer(dev); |
| break; |
| default: |
| ret = -EINVAL; |
| LOG_INST_ERR(cfg->log, "isr: next transfer, unknown msg status(0x%x)", |
| data->msg_state); |
| break; |
| }; |
| |
| if (ret) { |
| data->msg_state = IT51XXX_I3CM_MSG_ERROR; |
| } |
| sys_write8(NEXT_TRANSFER, cfg->base + I3CM01_STATUS); |
| } |
| } |
| |
| static DEVICE_API(i3c, it51xxx_i3cm_api) = { |
| .i2c_api.transfer = it51xxx_i3cm_i2c_api_transfer, |
| #ifdef CONFIG_I2C_RTIO |
| .i2c_api.iodev_submit = i2c_iodev_submit_fallback, |
| #endif /* CONFIG_I2C_RTIO */ |
| |
| .configure = it51xxx_i3cm_configure, |
| .config_get = it51xxx_i3cm_config_get, |
| |
| .do_daa = it51xxx_i3cm_do_daa, |
| .do_ccc = it51xxx_i3cm_do_ccc, |
| |
| .i3c_device_find = it51xxx_i3cm_device_find, |
| |
| .i3c_xfers = it51xxx_i3cm_transfer, |
| |
| #ifdef CONFIG_I3C_USE_IBI |
| .ibi_hj_response = it51xxx_i3cm_ibi_hj_response, |
| .ibi_enable = it51xxx_i3cm_ibi_enable, |
| .ibi_disable = it51xxx_i3cm_ibi_disable, |
| #endif /* CONFIG_I3C_USE_IBI */ |
| #ifdef CONFIG_I3C_RTIO |
| .iodev_submit = i3c_iodev_submit_fallback, |
| #endif /* CONFIG_I3C_RTIO */ |
| }; |
| |
| #define IT51XXX_I3CM_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 struct i3c_device_desc it51xxx_i3cm_device_array_##n[] = \ |
| I3C_DEVICE_ARRAY_DT_INST(n); \ |
| static struct i3c_i2c_device_desc it51xxx_i3cm_i2c_device_array_##n[] = \ |
| I3C_I2C_DEVICE_ARRAY_DT_INST(n); \ |
| static void it51xxx_i3cm_config_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), 0, it51xxx_i3cm_isr, DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| }; \ |
| static const struct it51xxx_i3cm_config i3c_config_##n = { \ |
| .base = DT_INST_REG_ADDR(n), \ |
| .irq_config_func = it51xxx_i3cm_config_func_##n, \ |
| .irq_num = DT_INST_IRQN(n), \ |
| .common.dev_list.i3c = it51xxx_i3cm_device_array_##n, \ |
| .common.dev_list.num_i3c = ARRAY_SIZE(it51xxx_i3cm_device_array_##n), \ |
| .common.dev_list.i2c = it51xxx_i3cm_i2c_device_array_##n, \ |
| .common.dev_list.num_i2c = ARRAY_SIZE(it51xxx_i3cm_i2c_device_array_##n), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .io_channel = DT_INST_PROP(n, io_channel), \ |
| .clocks.i3c_pp_duty_cycle = DT_INST_PROP_OR(n, i3c_pp_duty_cycle, 0), \ |
| .clocks.i3c_od_scl_hz = DT_INST_PROP_OR(n, i3c_od_scl_hz, 0), \ |
| .clocks.i3c_scl_hddat = DT_INST_PROP_OR(n, i3c_scl_hddat, 0), \ |
| .clocks.i3c_scl_tcas = DT_INST_PROP_OR(n, i3c_scl_tcas, 1), \ |
| .clocks.i3c_scl_tcbs = DT_INST_PROP_OR(n, i3c_scl_tcbs, 0), \ |
| .clocks.i3c_scl_tcasr = DT_INST_PROP_OR(n, i3c_scl_tcasr, 1), \ |
| .clocks.i3c_scl_tcbsr = DT_INST_PROP_OR(n, i3c_scl_tcbsr, 0), \ |
| .clocks.i2c_scl_hddat = DT_INST_PROP_OR(n, i2c_scl_hddat, 0), \ |
| LOG_INSTANCE_PTR_INIT(log, DT_NODE_FULL_NAME_TOKEN(DT_DRV_INST(n)), n)}; \ |
| static struct it51xxx_i3cm_data i3c_data_##n = { \ |
| .common.ctrl_config.scl.i3c = DT_INST_PROP_OR(n, i3c_scl_hz, 0), \ |
| .common.ctrl_config.scl.i2c = DT_INST_PROP_OR(n, i2c_scl_hz, 0), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, it51xxx_i3cm_init, NULL, &i3c_data_##n, &i3c_config_##n, \ |
| POST_KERNEL, CONFIG_I3C_CONTROLLER_INIT_PRIORITY, \ |
| &it51xxx_i3cm_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(IT51XXX_I3CM_INIT) |