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:
parent
86f54b79b7
commit
42a509b812
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
32
tests/drivers/i2c/i2c_target_api/boards/npcx9m6f_evb.overlay
Normal file
32
tests/drivers/i2c/i2c_target_api/boards/npcx9m6f_evb.overlay
Normal 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";
|
||||
};
|
Loading…
Reference in a new issue