driver: npcx: i2c: add i2c target mode support for npcx i2c drivers

Add I2C target mode support for NPCX i2c driver. Verified with
i2c_target_api test suite on npcx9m6_evb.

Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
This commit is contained in:
Mulin Chao 2023-04-18 23:28:53 -07:00 committed by Anas Nashif
parent 86f54b79b7
commit 42a509b812
4 changed files with 301 additions and 0 deletions

View file

@ -71,6 +71,7 @@
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <soc.h>
#include <zephyr/logging/log.h>
@ -111,6 +112,11 @@ enum npcx_i2c_freq {
NPCX_I2C_BUS_SPEED_1MHZ,
};
enum npcx_i2c_flag {
NPCX_I2C_FLAG_TARGET,
NPCX_I2C_FLAG_COUNT,
};
/*
* Internal SMBus Interface driver states values, which reflect events
* which occurred on the bus
@ -155,6 +161,10 @@ struct i2c_ctrl_data {
uint8_t port; /* current port used the controller */
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;
#endif
};
/* Driver convenience defines */
@ -737,6 +747,110 @@ static int i2c_ctrl_proc_read_msg(const struct device *dev, struct i2c_msg *msg)
return data->trans_err;
}
/* I2C controller isr function */
#ifdef CONFIG_I2C_TARGET
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;
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);
/* Notify upper layer the end of transaction */
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
}
/* Reset i2c module in target mode */
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE);
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE);
/* End of transaction */
data->oper_state = NPCX_I2C_IDLE;
return;
}
/* A 'Slave Stop' Condition has been identified */
if (IS_BIT_SET(status, NPCX_SMBST_SLVSTP)) {
/* Clear SLVSTP Bit */
inst->SMBST = BIT(NPCX_SMBST_SLVSTP);
/* 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);
}
return;
}
/* A negative acknowledge has occurred */
if (IS_BIT_SET(status, NPCX_SMBST_NEGACK)) {
/* Clear NEGACK Bit */
inst->SMBST = BIT(NPCX_SMBST_NEGACK);
/* Do nothing in i2c target mode */
return;
}
/* A 'Target Address Match' has been identified */
if (IS_BIT_SET(status, NPCX_SMBST_NMATCH)) {
/* Clear NMATCH Bit */
inst->SMBST = BIT(NPCX_SMBST_NMATCH);
/* Distinguish tje 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);
}
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);
}
}
return;
}
/* Tx byte empty or Rx byte full has occurred */
if (IS_BIT_SET(status, NPCX_SMBST_SDAST)) {
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);
}
inst->SMBSDA = val;
} else if (data->oper_state == NPCX_I2C_READ_FIFO) {
if (target_cb->write_received) {
val = inst->SMBSDA;
/* Notify upper layer one byte received */
target_cb->write_received(data->target_cfg, val);
}
} else {
LOG_ERR("Unexpected oper state %d on i2c target port%02x!",
data->oper_state, data->port);
}
return;
}
/* Clear unexpected status bits */
if (status != 0) {
inst->SMBST = status;
LOG_ERR("Unexpected SMBST 0x%02x occurred on i2c target port%02x!",
status, data->port);
}
}
#endif
/* I2C controller isr function */
static void i2c_ctrl_isr(const struct device *dev)
{
@ -747,6 +861,12 @@ static void i2c_ctrl_isr(const struct device *dev)
status = inst->SMBST & NPCX_VALID_SMBST_MASK;
LOG_DBG("status: %02x, %d", status, data->oper_state);
#ifdef CONFIG_I2C_TARGET
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
return i2c_ctrl_target_isr(dev, status);
}
#endif
/* A 'Bus Error' has been identified */
if (IS_BIT_SET(status, NPCX_SMBST_BER)) {
/* Generate a STOP condition immediately */
@ -937,6 +1057,86 @@ recover_exit:
return ret;
}
#ifdef CONFIG_I2C_TARGET
int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
struct i2c_target_config *target_cfg, uint8_t port)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(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;
}
/* 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;
i2c_ctrl_irq_enable(i2c_dev, 0);
/* Switch correct port for i2c controller first */
npcx_pinctrl_i2c_port_sel(idx_ctrl, idx_port);
/* Reset I2C module */
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE);
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE);
/* 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);
i2c_ctrl_irq_enable(i2c_dev, 1);
return 0;
}
int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
struct i2c_target_config *target_cfg)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(i2c_dev);
struct i2c_ctrl_data *const data = i2c_dev->data;
/* No I2c module has been configured to target mode */
if (!atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
return -EINVAL;
}
/* A transiaction is ongoing */
if (data->oper_state != NPCX_I2C_IDLE) {
return -EBUSY;
}
data->target_cfg = NULL;
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);
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
i2c_ctrl_irq_enable(i2c_dev, 1);
/* Mark it as controller mode */
atomic_clear_bit(&data->flags, NPCX_I2C_FLAG_TARGET);
return 0;
}
#endif
int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs,
uint8_t num_msgs, uint16_t addr, int port)
{
@ -944,6 +1144,13 @@ int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs,
int ret = 0;
uint8_t i;
#ifdef CONFIG_I2C_TARGET
/* I2c module has been configured to target mode */
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
return -EBUSY;
}
#endif
/*
* suspend-to-idle stops SMB module clocks (derived from APB2/APB3), which must remain
* active during a transaction

View file

@ -80,6 +80,32 @@ int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs,
*/
int npcx_i2c_ctrl_recover_bus(const struct device *dev);
/**
* @brief Registers the provided config as Target device of a npcx i2c controller.
*
* @param i2c_dev Pointer to the device structure for i2c controller instance.
* @param target_cfg Config struct used by the i2c target driver
* @param port Port index of selected i2c port.
*
* @retval 0 Is successful
* @retval -EBUSY If i2c transaction is proceeding.
*/
int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
struct i2c_target_config *target_cfg, uint8_t port);
/**
* @brief Unregisters the provided config as Target device of a npcx i2c controller.
*
* @param i2c_dev Pointer to the device structure for i2c controller instance.
* @param target_cfg Config struct used by the i2c target driver
*
* @retval 0 Is successful
* @retval -EBUSY If i2c transaction is proceeding.
* @retval -EINVAL If parameters are invalid
*/
int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
struct i2c_target_config *target_cfg);
#ifdef __cplusplus
}
#endif

View file

@ -143,6 +143,38 @@ static int i2c_npcx_port_recover_bus(const struct device *dev)
return ret;
}
#ifdef CONFIG_I2C_TARGET
static int i2c_npcx_target_register(const struct device *dev,
struct i2c_target_config *target_cfg)
{
const struct i2c_npcx_port_config *const config = dev->config;
if (!target_cfg) {
return -EINVAL;
}
if (config->i2c_ctrl == NULL) {
LOG_ERR("Cannot find i2c controller on port%02x!", config->port);
return -EIO;
}
return npcx_i2c_ctrl_target_register(config->i2c_ctrl, target_cfg, config->port);
}
static int i2c_npcx_target_unregister(const struct device *dev,
struct i2c_target_config *target_cfg)
{
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);
}
#endif
/* I2C driver registration */
static int i2c_npcx_port_init(const struct device *dev)
{
@ -173,6 +205,10 @@ static const struct i2c_driver_api i2c_port_npcx_driver_api = {
.get_config = i2c_npcx_port_get_config,
.transfer = i2c_npcx_port_transfer,
.recover_bus = i2c_npcx_port_recover_bus,
#ifdef CONFIG_I2C_TARGET
.target_register = i2c_npcx_target_register,
.target_unregister = i2c_npcx_target_unregister,
#endif
};
/* I2C port init macro functions */

View file

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: Apache-2.0 */
/ {
chosen {
zephyr,shell-uart = &uart1;
};
};
&i2c0_0 {
eeprom0: eeprom@54 {
compatible = "zephyr,i2c-target-eeprom";
reg = <0x54>;
size = <1024>;
};
};
&i2c2_0 {
status = "okay";
pinctrl-0 = <&i2c2_0_sda_scl_gp91_92>;
pinctrl-names = "default";
clock-frequency = <I2C_BITRATE_FAST>;
eeprom1: eeprom@56 {
compatible = "zephyr,i2c-target-eeprom";
reg = <0x56>;
size = <1024>;
};
};
&i2c_ctrl2 {
status = "okay";
};