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:
parent
a15cc2d37c
commit
79b526f65a
@ -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 */
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user