Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
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

/*
* 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)