Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: i2c: npcx: target mode support multi address #86540

Merged
merged 1 commit into from
Mar 25, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
alvsun committed Mar 21, 2025
commit 430d644177dc51ee822308d22f26283abf19e293
216 changes: 168 additions & 48 deletions drivers/i2c/i2c_npcx_controller.c
Original file line number Diff line number Diff line change
@@ -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,17 +857,19 @@ 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 */
if (IS_BIT_SET(status, NPCX_SMBST_BER)) {
/* 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,39 +923,56 @@ 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;
}

/* 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 @@ int npcx_i2c_ctrl_recover_bus(const struct device *dev)
}

#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,44 +1316,78 @@ 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;
}

/* A transiaction is ongoing */
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);
/* Mark it as controller mode */
atomic_clear_bit(&data->registered_target_mask, cur_addr_slot);

/* 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);
/* 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);
/* Mark it as controller mode */
atomic_clear_bit(&data->flags, NPCX_I2C_FLAG_TARGET);

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 */
22 changes: 20 additions & 2 deletions drivers/i2c/i2c_npcx_port.c
Original file line number Diff line number Diff line change
@@ -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,20 +159,37 @@ 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) {
LOG_ERR("Cannot find i2c controller on port%02x!", config->port);
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