drivers: i2c: npcx: add support for multi-address in target mode

This commit introduces support for the NPCX I2C driver to handle up to
8 addresses in target mode

Signed-off-by: Alvis Sun <yfsun@nuvoton.com>
This commit is contained in:
Alvis Sun 2025-02-19 10:02:15 +08:00 committed by Benjamin Cabé
parent a15cc2d37c
commit 79b526f65a
2 changed files with 191 additions and 53 deletions

View File

@ -108,6 +108,8 @@ LOG_MODULE_REGISTER(i2c_npcx, CONFIG_I2C_LOG_LEVEL);
#define I2C_RECOVER_SCL_RETRY 10
#define I2C_RECOVER_SDA_RETRY 3
#define NPCX_SMBADDR_SAEN NPCX_SMBADDR1_SAEN /* All the SAEN in SMBADDR is bit_7 */
/* Supported I2C bus frequency */
enum npcx_i2c_freq {
NPCX_I2C_BUS_SPEED_100KHZ,
@ -116,7 +118,14 @@ enum npcx_i2c_freq {
};
enum npcx_i2c_flag {
NPCX_I2C_FLAG_TARGET,
NPCX_I2C_FLAG_TARGET1,
NPCX_I2C_FLAG_TARGET2,
NPCX_I2C_FLAG_TARGET3,
NPCX_I2C_FLAG_TARGET4,
NPCX_I2C_FLAG_TARGET5,
NPCX_I2C_FLAG_TARGET6,
NPCX_I2C_FLAG_TARGET7,
NPCX_I2C_FLAG_TARGET8,
NPCX_I2C_FLAG_COUNT,
};
@ -178,8 +187,9 @@ struct i2c_ctrl_data {
bool is_configured; /* is port configured? */
const struct npcx_i2c_timing_cfg *ptr_speed_confs;
#ifdef CONFIG_I2C_TARGET
struct i2c_target_config *target_cfg;
atomic_t flags;
struct i2c_target_config *target_cfg[NPCX_I2C_FLAG_COUNT];
uint8_t target_idx; /* current target_cfg index */
atomic_t registered_target_mask;
/* i2c wake-up callback configuration */
struct miwu_callback smb_wk_cb;
#endif /* CONFIG_I2C_TARGET */
@ -847,7 +857,7 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
struct i2c_ctrl_data *const data = dev->data;
const struct i2c_target_callbacks *target_cb = data->target_cfg->callbacks;
const struct i2c_target_callbacks *target_cb = NULL;
uint8_t val = 0;
/* A 'Bus Error' has been identified */
@ -855,9 +865,11 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
/* Clear BER Bit */
inst->SMBST = BIT(NPCX_SMBST_BER);
target_cb = data->target_cfg[data->target_idx]->callbacks;
/* Notify upper layer the end of transaction */
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
if ((target_cb != NULL) && target_cb->stop) {
target_cb->stop(data->target_cfg[data->target_idx]);
}
/* Reset i2c module in target mode */
@ -887,8 +899,9 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
/* End of transaction */
data->oper_state = NPCX_I2C_IDLE;
/* Notify upper layer a STOP condition received */
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
target_cb = data->target_cfg[data->target_idx]->callbacks;
if ((target_cb != NULL) && target_cb->stop) {
target_cb->stop(data->target_cfg[data->target_idx]);
}
#ifdef CONFIG_PM
@ -910,21 +923,36 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
/* Clear NMATCH Bit */
inst->SMBST = BIT(NPCX_SMBST_NMATCH);
/* Check MATCH1F ~ MATCH7F */
if (inst->SMBCST2 & ~BIT(NPCX_SMBCST2_INTSTS)) {
for (uint8_t addr_idx = NPCX_I2C_FLAG_TARGET1;
addr_idx <= NPCX_I2C_FLAG_TARGET7; addr_idx++) {
if (inst->SMBCST2 & BIT(addr_idx)) {
data->target_idx = addr_idx;
break;
}
}
} else if (inst->SMBCST3 & BIT(NPCX_SMBCST3_MATCHA8F)) {
data->target_idx = NPCX_I2C_FLAG_TARGET8;
}
target_cb = data->target_cfg[data->target_idx]->callbacks;
/* Distinguish the direction of i2c target mode by reading XMIT bit */
if (IS_BIT_SET(inst->SMBST, NPCX_SMBST_XMIT)) {
/* Start transmitting data in i2c target mode */
data->oper_state = NPCX_I2C_WRITE_FIFO;
/* Write first requested byte after repeated start */
if (target_cb->read_requested) {
target_cb->read_requested(data->target_cfg, &val);
if ((target_cb != NULL) && target_cb->read_requested) {
target_cb->read_requested(data->target_cfg[data->target_idx], &val);
}
inst->SMBSDA = val;
} else {
/* Start receiving data in i2c target mode */
data->oper_state = NPCX_I2C_READ_FIFO;
if (target_cb->write_requested) {
target_cb->write_requested(data->target_cfg);
if ((target_cb != NULL) && target_cb->write_requested) {
target_cb->write_requested(data->target_cfg[data->target_idx]);
}
}
return;
@ -932,17 +960,19 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
/* Tx byte empty or Rx byte full has occurred */
if (IS_BIT_SET(status, NPCX_SMBST_SDAST)) {
target_cb = data->target_cfg[data->target_idx]->callbacks;
if (data->oper_state == NPCX_I2C_WRITE_FIFO) {
/* Notify upper layer one byte will be transmitted */
if (target_cb->read_processed) {
target_cb->read_processed(data->target_cfg, &val);
if ((target_cb != NULL) && target_cb->read_processed) {
target_cb->read_processed(data->target_cfg[data->target_idx], &val);
}
inst->SMBSDA = val;
} else if (data->oper_state == NPCX_I2C_READ_FIFO) {
if (target_cb->write_received) {
if ((target_cb != NULL) && target_cb->write_received) {
val = inst->SMBSDA;
/* Notify upper layer one byte received */
target_cb->write_received(data->target_cfg, val);
target_cb->write_received(data->target_cfg[data->target_idx], val);
}
} else {
LOG_ERR("Unexpected oper state %d on i2c target port%02x!",
@ -971,7 +1001,7 @@ static void i2c_ctrl_isr(const struct device *dev)
LOG_DBG("status: %02x, %d", status, data->oper_state);
#ifdef CONFIG_I2C_TARGET
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
if (atomic_get(&data->registered_target_mask) != (atomic_val_t) 0) {
i2c_ctrl_target_isr(dev, status);
return;
}
@ -1170,6 +1200,34 @@ recover_exit:
}
#ifdef CONFIG_I2C_TARGET
static volatile uint8_t *npcx_i2c_ctrl_target_get_reg_smbaddr(const struct device *i2c_dev,
int index)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(i2c_dev);
switch (index) {
case 0:
return &inst->SMBADDR1;
case 1:
return &inst->SMBADDR2;
case 2:
return &inst->SMBADDR3;
case 3:
return &inst->SMBADDR4;
case 4:
return &inst->SMBADDR5;
case 5:
return &inst->SMBADDR6;
case 6:
return &inst->SMBADDR7;
case 7:
return &inst->SMBADDR8;
default:
LOG_ERR("Invalid SMBADDR index: %d", index);
return NULL;
}
}
int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
struct i2c_target_config *target_cfg, uint8_t port)
{
@ -1178,22 +1236,50 @@ int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
struct i2c_ctrl_data *const data = i2c_dev->data;
int idx_ctrl = (port & 0xF0) >> 4;
int idx_port = (port & 0x0F);
uint8_t addr = BIT(NPCX_SMBADDR1_SAEN) | target_cfg->address;
/* I2c module has been configured to target mode */
if (atomic_test_and_set_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
return -EBUSY;
}
int avail_addr_slot;
volatile uint8_t *reg_smbaddr;
uint8_t smbaddr_val = BIT(NPCX_SMBADDR_SAEN) | target_cfg->address;
uint32_t i2c_tgt_mask = (uint32_t)atomic_get(&data->registered_target_mask);
int addr_idx;
/* A transiaction is ongoing */
if (data->oper_state != NPCX_I2C_IDLE) {
atomic_clear_bit(&data->flags, NPCX_I2C_FLAG_TARGET);
return -EBUSY;
}
data->target_cfg = target_cfg;
/* Find valid smbaddr location */
avail_addr_slot = find_lsb_set(~i2c_tgt_mask) - 1;
if (avail_addr_slot == -1 || avail_addr_slot >= NPCX_I2C_FLAG_COUNT) {
LOG_ERR("No available smbaddr register, smbaddr_idx: %d", avail_addr_slot);
return -ENOSPC;
}
/* Check if the address is duplicated */
while (i2c_tgt_mask) {
addr_idx = find_lsb_set(i2c_tgt_mask) - 1;
reg_smbaddr = npcx_i2c_ctrl_target_get_reg_smbaddr(i2c_dev, addr_idx);
/* Check if the address is duplicated */
if (*reg_smbaddr == smbaddr_val) {
LOG_ERR("Address %#x is already set", target_cfg->address);
return -EINVAL;
}
i2c_tgt_mask &= ~BIT(addr_idx);
}
/* Mark the selected address slot */
atomic_set_bit(&data->registered_target_mask, avail_addr_slot);
i2c_ctrl_irq_enable(i2c_dev, 0);
data->port = port; /* Update the I2C port index */
/* Config new address */
reg_smbaddr = npcx_i2c_ctrl_target_get_reg_smbaddr(i2c_dev, avail_addr_slot);
*reg_smbaddr = smbaddr_val; /* Set address register */
data->target_cfg[avail_addr_slot] = target_cfg; /* Set target config */
/* Switch correct port for i2c controller first */
npcx_pinctrl_i2c_port_sel(idx_ctrl, idx_port);
/* Reset I2C module */
@ -1203,7 +1289,6 @@ int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
/* Select normal bank and single byte mode for i2c target mode */
i2c_ctrl_bank_sel(i2c_dev, NPCX_I2C_BANK_NORMAL);
inst->SMBFIF_CTL &= ~BIT(NPCX_SMBFIF_CTL_FIFO_EN);
inst->SMBADDR1 = addr; /* Enable target mode and configure its address */
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
@ -1218,6 +1303,7 @@ int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
/* Enable SMB's MIWU interrupts */
npcx_miwu_irq_enable(&config->smb_wui);
}
i2c_ctrl_irq_enable(i2c_dev, 1);
return 0;
@ -1230,9 +1316,14 @@ int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
const struct i2c_ctrl_config *const config = i2c_dev->config;
struct i2c_ctrl_data *const data = i2c_dev->data;
int idx_ctrl = (port & 0xF0) >> 4;
int cur_addr_slot;
volatile uint8_t *reg_smbaddr;
uint8_t smbaddr_val = BIT(NPCX_SMBADDR_SAEN) | target_cfg->address;
uint32_t i2c_tgt_mask = (uint32_t)atomic_get(&data->registered_target_mask);
/* No I2c module has been configured to target mode */
if (!atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
if (atomic_get(&data->registered_target_mask) == (atomic_val_t) 0) {
LOG_ERR("No available target to ungister");
return -EINVAL;
}
@ -1240,34 +1331,63 @@ int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
if (data->oper_state != NPCX_I2C_IDLE) {
return -EBUSY;
}
data->target_cfg = NULL;
/* Find target address in smbaddr */
while (i2c_tgt_mask) {
cur_addr_slot = find_lsb_set(i2c_tgt_mask) - 1;
reg_smbaddr = npcx_i2c_ctrl_target_get_reg_smbaddr(i2c_dev, cur_addr_slot);
if (reg_smbaddr == NULL) {
LOG_ERR("Invalid smbaddr register");
return -EINVAL;
}
/* Target address found */
if (*reg_smbaddr == smbaddr_val) {
break;
}
i2c_tgt_mask &= ~BIT(cur_addr_slot);
}
/* Input addrss is not in the smbaddr */
if (i2c_tgt_mask == 0 || reg_smbaddr == NULL) {
LOG_ERR("Address %#x is not found", target_cfg->address);
return -EINVAL;
}
i2c_ctrl_irq_enable(i2c_dev, 0);
/* Reset I2C module */
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE);
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE);
inst->SMBADDR1 = 0; /* Disable target mode and clear address setting */
/* Enable FIFO mode and select to FIFO bank for i2c controller mode */
inst->SMBFIF_CTL |= BIT(NPCX_SMBFIF_CTL_FIFO_EN);
i2c_ctrl_bank_sel(i2c_dev, NPCX_I2C_BANK_FIFO);
*reg_smbaddr = 0; /* Disable target mode and clear address setting */
data->target_cfg[cur_addr_slot] = NULL; /* Clear target config */
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
/* Disable irq of smb wake-up event */
if (IS_ENABLED(CONFIG_PM)) {
/* Disable SMB wake up detection */
npcx_i2c_target_start_wk_enable(idx_ctrl, false);
/* Disable start detect in IDLE */
inst->SMBCTL3 &= ~BIT(NPCX_SMBCTL3_IDL_START);
/* Disable SMB's MIWU interrupts */
npcx_miwu_irq_disable(&config->smb_wui);
}
i2c_ctrl_irq_enable(i2c_dev, 1);
/* Mark it as controller mode */
atomic_clear_bit(&data->flags, NPCX_I2C_FLAG_TARGET);
atomic_clear_bit(&data->registered_target_mask, cur_addr_slot);
/* Switch I2C to controller mode if no any other valid address in smbaddr */
if (atomic_get(&data->registered_target_mask) == (atomic_val_t) 0) {
/* Reset I2C module */
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE);
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE);
/* Enable FIFO mode and select to FIFO bank for i2c controller mode */
inst->SMBFIF_CTL |= BIT(NPCX_SMBFIF_CTL_FIFO_EN);
i2c_ctrl_bank_sel(i2c_dev, NPCX_I2C_BANK_FIFO);
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
/* Disable irq of smb wake-up event */
if (IS_ENABLED(CONFIG_PM)) {
/* Disable SMB wake up detection */
npcx_i2c_target_start_wk_enable(idx_ctrl, false);
/* Disable start detect in IDLE */
inst->SMBCTL3 &= ~BIT(NPCX_SMBCTL3_IDL_START);
/* Disable SMB's MIWU interrupts */
npcx_miwu_irq_disable(&config->smb_wui);
}
}
i2c_ctrl_irq_enable(i2c_dev, 1);
return 0;
}
@ -1304,7 +1424,7 @@ int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs,
#ifdef CONFIG_I2C_TARGET
/* I2c module has been configured to target mode */
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
if (atomic_get(&data->registered_target_mask) != (atomic_val_t) 0) {
return -EBUSY;
}
#endif /* CONFIG_I2C_TARGET */

View File

@ -147,6 +147,7 @@ static int i2c_npcx_port_recover_bus(const struct device *dev)
static int i2c_npcx_target_register(const struct device *dev,
struct i2c_target_config *target_cfg)
{
int ret;
const struct i2c_npcx_port_config *const config = dev->config;
if (!target_cfg) {
@ -158,12 +159,21 @@ static int i2c_npcx_target_register(const struct device *dev,
return -EIO;
}
return npcx_i2c_ctrl_target_register(config->i2c_ctrl, target_cfg, config->port);
/* Lock mutex of i2c/smb controller */
npcx_i2c_ctrl_mutex_lock(config->i2c_ctrl);
ret = npcx_i2c_ctrl_target_register(config->i2c_ctrl, target_cfg, config->port);
/* Unlock mutex of i2c/smb controller */
npcx_i2c_ctrl_mutex_unlock(config->i2c_ctrl);
return ret;
}
static int i2c_npcx_target_unregister(const struct device *dev,
struct i2c_target_config *target_cfg)
{
int ret;
const struct i2c_npcx_port_config *const config = dev->config;
if (config->i2c_ctrl == NULL) {
@ -171,7 +181,15 @@ static int i2c_npcx_target_unregister(const struct device *dev,
return -EIO;
}
return npcx_i2c_ctrl_target_unregister(config->i2c_ctrl, target_cfg, config->port);
/* Lock mutex of i2c/smb controller */
npcx_i2c_ctrl_mutex_lock(config->i2c_ctrl);
ret = npcx_i2c_ctrl_target_unregister(config->i2c_ctrl, target_cfg, config->port);
/* Unlock mutex of i2c/smb controller */
npcx_i2c_ctrl_mutex_unlock(config->i2c_ctrl);
return ret;
}
#endif