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
Show file tree
Hide file tree
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
216 changes: 168 additions & 48 deletions drivers/i2c/i2c_npcx_controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
};

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand All @@ -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!",
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
{
Expand All @@ -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 */
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 */
Expand Down
Loading
Loading