You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
883 lines
28 KiB
883 lines
28 KiB
/* |
|
* Copyright (c) 2025 ITE Technology Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT ite_it51xxx_i3cs |
|
|
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/logging/log_instance.h> |
|
LOG_MODULE_REGISTER(i3cs_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 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)
|
|
|