You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1628 lines
48 KiB
1628 lines
48 KiB
/* |
|
* Copyright (c) 2020 Nuvoton Technology Corporation. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
#include <zephyr/pm/policy.h> |
|
|
|
#define DT_DRV_COMPAT nuvoton_npcx_i2c_ctrl |
|
|
|
/** |
|
* @file |
|
* @brief Nuvoton NPCX smb/i2c module (controller) driver |
|
* |
|
* This file contains the driver of SMB module (controller) which provides full |
|
* support for a two-wire SMBus/I2C synchronous serial interface. The following |
|
* is the state diagrams for each Zephyr i2c api functions. |
|
* |
|
* case 1: i2c_write()/i2c_burst_write() |
|
* |
|
* All msg data sent? Is there next msg? |
|
* +<----------------+<----------------------+ |
|
* | No | | Yes |
|
* +------+ +------------+ | +------- ----+ | +------- -------+ | |
|
* +->| IDLE |-->| WAIT_START |--->| WRITE_FIFO |-+--->| WRITE_SUSPEND |--+ |
|
* | +------+ +------------+ +------------+ Yes +---------------+ | |
|
* | Issue START START completed | No |
|
* | +-----------+ | |
|
* +--------------------------------------------| WAIT_STOP |<------------+ |
|
* STOP is completed +-----------+ Issue STOP |
|
* |
|
* |
|
* case 2: i2c_read() |
|
* |
|
* All msg data received? Is there next msg? |
|
* +<-----------------+<---------------------+ |
|
* | No | | Yes |
|
* +------+ +------------+ | +------- ---+ | +------- ------+ | |
|
* +->| IDLE |-->| WAIT_START |--->| READ_FIFO |---+--->| READ_SUSPEND |--+ |
|
* | +------+ +------------+ +------------+ Yes +--------------+ | |
|
* | Issue START START completed | No |
|
* | +-----------+ | |
|
* +------------------------------------------| WAIT_STOP |<--------------+ |
|
* STOP is completed +-----------+ Issue STOP |
|
* |
|
* |
|
* case 3: i2c_write_read()/i2c_burst_read() |
|
* |
|
* All msg data sent? Is there next write msg? |
|
* +<----------------+<----------------------+ |
|
* | No | | Yes |
|
* +------+ +------------+ | +------- ----+ | +------- -------+ | |
|
* +->| IDLE |-->| WAIT_START |--->| WRITE_FIFO |-+--->| WRITE_SUSPEND |--+ |
|
* | +------+ +------------+ +------------+ Yes +---------------+ | |
|
* | Issue START START completed | No |
|
* | +---------------------------------------------------------------+ |
|
* | | |
|
* | | All msg data received? Is there next read msg? |
|
* | | +<-----------------+<-----------------------+ |
|
* | | | No | | Yes |
|
* | | +--------------+ | +------- ---+ | +------- ------+ | |
|
* | +--| WAIT_RESTART |--->| READ_FIFO |---+--->| READ_SUSPEND |----+ |
|
* | +--------------+ +-----------+ Yes +--------------+ | |
|
* | Issue RESTART RESTART completed | No |
|
* | +-----------+ | |
|
* +-------------------------------------------| WAIT_STOP |<-------------+ |
|
* STOP is completed +-----------+ Issue STOP |
|
* |
|
*/ |
|
|
|
#include <assert.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/i2c.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/atomic.h> |
|
#include <soc.h> |
|
#include "soc_miwu.h" |
|
#include "soc_pins.h" |
|
#include "soc_power.h" |
|
|
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/irq.h> |
|
LOG_MODULE_REGISTER(i2c_npcx, CONFIG_I2C_LOG_LEVEL); |
|
|
|
/* I2C controller mode */ |
|
#define NPCX_I2C_BANK_NORMAL 0 |
|
#define NPCX_I2C_BANK_FIFO 1 |
|
|
|
/* Timeout for device should be available after reset (SMBus spec. unit:ms) */ |
|
#define I2C_MAX_TIMEOUT 35 |
|
|
|
/* Timeout for SCL held to low by slave device . (SMBus spec. unit:ms). */ |
|
#define I2C_MIN_TIMEOUT 25 |
|
|
|
/* Default maximum time we allow for an I2C transfer (unit:ms) */ |
|
#define I2C_TRANS_TIMEOUT K_MSEC(100) |
|
|
|
/* |
|
* NPCX I2C module that supports FIFO mode has 32 bytes Tx FIFO and |
|
* 32 bytes Rx FIFO. |
|
*/ |
|
#define NPCX_I2C_FIFO_MAX_SIZE 32 |
|
|
|
/* Valid bit fields in SMBST register */ |
|
#define NPCX_VALID_SMBST_MASK ~(BIT(NPCX_SMBST_XMIT) | BIT(NPCX_SMBST_MASTER)) |
|
|
|
/* The delay for the I2C bus recovery bitbang in ~100K Hz */ |
|
#define I2C_RECOVER_BUS_DELAY_US 5 |
|
#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, |
|
NPCX_I2C_BUS_SPEED_400KHZ, |
|
NPCX_I2C_BUS_SPEED_1MHZ, |
|
}; |
|
|
|
enum npcx_i2c_flag { |
|
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, |
|
}; |
|
|
|
enum i2c_pm_policy_state_flag { |
|
I2C_PM_POLICY_STATE_FLAG_TGT, |
|
I2C_PM_POLICY_STATE_FLAG_COUNT, |
|
}; |
|
|
|
/* |
|
* Internal SMBus Interface driver states values, which reflect events |
|
* which occurred on the bus |
|
*/ |
|
enum npcx_i2c_oper_state { |
|
NPCX_I2C_IDLE, |
|
NPCX_I2C_WAIT_START, |
|
NPCX_I2C_WAIT_RESTART, |
|
NPCX_I2C_WRITE_FIFO, |
|
NPCX_I2C_WRITE_SUSPEND, |
|
NPCX_I2C_READ_FIFO, |
|
NPCX_I2C_READ_SUSPEND, |
|
NPCX_I2C_WAIT_STOP, |
|
NPCX_I2C_ERROR_RECOVERY, |
|
}; |
|
|
|
/* I2C timing configuration for each i2c speed */ |
|
struct npcx_i2c_timing_cfg { |
|
uint8_t HLDT; /* i2c hold-time (Unit: clocks) */ |
|
uint8_t k1; /* k1 = SCL low-time (Unit: clocks) */ |
|
uint8_t k2; /* k2 = SCL high-time (Unit: clocks) */ |
|
}; |
|
|
|
/* Device config */ |
|
struct i2c_ctrl_config { |
|
uintptr_t base; /* i2c controller base address */ |
|
struct npcx_clk_cfg clk_cfg; /* clock configuration */ |
|
uint8_t irq; /* i2c controller irq */ |
|
#ifdef CONFIG_I2C_TARGET |
|
/* i2c wake-up input source configuration */ |
|
const struct npcx_wui smb_wui; |
|
bool wakeup_source; |
|
#endif /* CONFIG_I2C_TARGET */ |
|
}; |
|
|
|
/* Driver data */ |
|
struct i2c_ctrl_data { |
|
struct k_sem lock_sem; /* mutex of i2c controller */ |
|
struct k_sem sync_sem; /* semaphore used for synchronization */ |
|
uint32_t bus_freq; /* operation freq of i2c */ |
|
enum npcx_i2c_oper_state oper_state; /* controller operation state */ |
|
int trans_err; /* error code during transaction */ |
|
struct i2c_msg *msg; /* cache msg for transaction state machine */ |
|
struct i2c_msg *msg_head; |
|
int is_write; /* direction of current msg */ |
|
uint8_t *ptr_msg; /* current msg pointer for FIFO read/write */ |
|
uint16_t addr; /* slave address of transaction */ |
|
uint8_t msg_max_num; |
|
uint8_t msg_curr_idx; |
|
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[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 */ |
|
|
|
#if defined(CONFIG_PM) && defined(CONFIG_I2C_TARGET) |
|
ATOMIC_DEFINE(pm_policy_state_flag, I2C_PM_POLICY_STATE_FLAG_COUNT); |
|
#endif /* CONFIG_PM && CONFIG_I2C_TARGET */ |
|
}; |
|
|
|
/* Driver convenience defines */ |
|
#define HAL_I2C_INSTANCE(dev) \ |
|
((struct smb_reg *)((const struct i2c_ctrl_config *)(dev)->config)->base) |
|
|
|
/* Recommended I2C timing values are based on 15 MHz */ |
|
static const struct npcx_i2c_timing_cfg npcx_15m_speed_confs[] = { |
|
[NPCX_I2C_BUS_SPEED_100KHZ] = {.HLDT = 15, .k1 = 76, .k2 = 0}, |
|
[NPCX_I2C_BUS_SPEED_400KHZ] = {.HLDT = 7, .k1 = 24, .k2 = 18,}, |
|
[NPCX_I2C_BUS_SPEED_1MHZ] = {.HLDT = 7, .k1 = 14, .k2 = 10,}, |
|
}; |
|
|
|
static const struct npcx_i2c_timing_cfg npcx_20m_speed_confs[] = { |
|
[NPCX_I2C_BUS_SPEED_100KHZ] = {.HLDT = 15, .k1 = 102, .k2 = 0}, |
|
[NPCX_I2C_BUS_SPEED_400KHZ] = {.HLDT = 7, .k1 = 32, .k2 = 22}, |
|
[NPCX_I2C_BUS_SPEED_1MHZ] = {.HLDT = 7, .k1 = 16, .k2 = 10}, |
|
}; |
|
|
|
#if defined(CONFIG_PM) && defined(CONFIG_I2C_TARGET) |
|
static void i2c_npcx_pm_policy_state_lock_get(const struct device *dev, |
|
enum i2c_pm_policy_state_flag flag) |
|
{ |
|
const struct i2c_ctrl_config *const config = dev->config; |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
if (!config->wakeup_source) { |
|
return; |
|
} |
|
|
|
if (atomic_test_and_set_bit(data->pm_policy_state_flag, flag) == 0) { |
|
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
|
} |
|
} |
|
|
|
static void i2c_npcx_pm_policy_state_lock_put(const struct device *dev, |
|
enum i2c_pm_policy_state_flag flag) |
|
{ |
|
const struct i2c_ctrl_config *const config = dev->config; |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
if (!config->wakeup_source) { |
|
return; |
|
} |
|
|
|
if (atomic_test_and_clear_bit(data->pm_policy_state_flag, flag) == 1) { |
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
|
} |
|
} |
|
#endif /* CONFIG_PM && CONFIG_I2C_TARGET */ |
|
|
|
/* I2C controller inline functions access shared registers */ |
|
static inline void i2c_ctrl_start(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_START); |
|
} |
|
|
|
static inline void i2c_ctrl_stop(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_STOP); |
|
} |
|
|
|
static inline int i2c_ctrl_bus_busy(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
return IS_BIT_SET(inst->SMBCST, NPCX_SMBCST_BB); |
|
} |
|
|
|
static inline void i2c_ctrl_bank_sel(const struct device *dev, int bank) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
if (bank) { |
|
inst->SMBCTL3 |= BIT(NPCX_SMBCTL3_BNK_SEL); |
|
} else { |
|
inst->SMBCTL3 &= ~BIT(NPCX_SMBCTL3_BNK_SEL); |
|
} |
|
} |
|
|
|
static inline void i2c_ctrl_irq_enable(const struct device *dev, int enable) |
|
{ |
|
const struct i2c_ctrl_config *const config = dev->config; |
|
|
|
if (enable) { |
|
irq_enable(config->irq); |
|
} else { |
|
irq_disable(config->irq); |
|
} |
|
} |
|
|
|
/* I2C controller inline functions access registers in 'Normal' bank */ |
|
static inline void i2c_ctrl_norm_stall_scl(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
/* Enable writing to SCL_LVL/SDA_LVL bit in SMBnCTL3 */ |
|
inst->SMBCTL4 |= BIT(NPCX_SMBCTL4_LVL_WE); |
|
/* Force SCL bus to low and keep SDA floating */ |
|
inst->SMBCTL3 = (inst->SMBCTL3 & ~BIT(NPCX_SMBCTL3_SCL_LVL)) |
|
| BIT(NPCX_SMBCTL3_SDA_LVL); |
|
/* Disable writing to them */ |
|
inst->SMBCTL4 &= ~BIT(NPCX_SMBCTL4_LVL_WE); |
|
} |
|
|
|
static inline void i2c_ctrl_norm_free_scl(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
/* Enable writing to SCL_LVL/SDA_LVL bit in SMBnCTL3 */ |
|
inst->SMBCTL4 |= BIT(NPCX_SMBCTL4_LVL_WE); |
|
/* |
|
* Release SCL bus. Then it might be still driven by module itself or |
|
* slave device. |
|
*/ |
|
inst->SMBCTL3 |= BIT(NPCX_SMBCTL3_SCL_LVL) | BIT(NPCX_SMBCTL3_SDA_LVL); |
|
/* Disable writing to them */ |
|
inst->SMBCTL4 &= ~BIT(NPCX_SMBCTL4_LVL_WE); |
|
} |
|
|
|
/* I2C controller inline functions access registers in 'Normal' bank */ |
|
static inline void i2c_ctrl_norm_stall_sda(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
/* Enable writing to SCL_LVL/SDA_LVL bit in SMBnCTL3 */ |
|
inst->SMBCTL4 |= BIT(NPCX_SMBCTL4_LVL_WE); |
|
/* Force SDA bus to low and keep SCL floating */ |
|
inst->SMBCTL3 = (inst->SMBCTL3 & ~BIT(NPCX_SMBCTL3_SDA_LVL)) |
|
| BIT(NPCX_SMBCTL3_SCL_LVL); |
|
/* Disable writing to them */ |
|
inst->SMBCTL4 &= ~BIT(NPCX_SMBCTL4_LVL_WE); |
|
} |
|
|
|
static inline void i2c_ctrl_norm_free_sda(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
/* Enable writing to SCL_LVL/SDA_LVL bit in SMBnCTL3 */ |
|
inst->SMBCTL4 |= BIT(NPCX_SMBCTL4_LVL_WE); |
|
/* |
|
* Release SDA bus. Then it might be still driven by module itself or |
|
* slave device. |
|
*/ |
|
inst->SMBCTL3 |= BIT(NPCX_SMBCTL3_SDA_LVL) | BIT(NPCX_SMBCTL3_SCL_LVL); |
|
/* Disable writing to them */ |
|
inst->SMBCTL4 &= ~BIT(NPCX_SMBCTL4_LVL_WE); |
|
} |
|
|
|
/* I2C controller inline functions access registers in 'FIFO' bank */ |
|
static inline void i2c_ctrl_fifo_write(const struct device *dev, uint8_t data) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
inst->SMBSDA = data; |
|
} |
|
|
|
static inline uint8_t i2c_ctrl_fifo_read(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
return inst->SMBSDA; |
|
} |
|
|
|
static inline int i2c_ctrl_fifo_tx_avail(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
return NPCX_I2C_FIFO_MAX_SIZE - (inst->SMBTXF_STS & 0x3f); |
|
} |
|
|
|
static inline int i2c_ctrl_fifo_rx_occupied(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
return inst->SMBRXF_STS & 0x3f; |
|
} |
|
|
|
static inline void i2c_ctrl_fifo_rx_setup_threshold_nack( |
|
const struct device *dev, int threshold, int last) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
uint8_t value = MIN(threshold, NPCX_I2C_FIFO_MAX_SIZE); |
|
|
|
SET_FIELD(inst->SMBRXF_CTL, NPCX_SMBRXF_CTL_RX_THR, value); |
|
|
|
/* |
|
* Is it last received transaction? If so, set LAST bit. Then the |
|
* hardware will generate NACK automatically when receiving last byte. |
|
*/ |
|
if (last && (value == threshold)) { |
|
inst->SMBRXF_CTL |= BIT(NPCX_SMBRXF_CTL_LAST); |
|
} |
|
} |
|
|
|
static inline void i2c_ctrl_fifo_clear_status(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
inst->SMBFIF_CTS |= BIT(NPCX_SMBFIF_CTS_CLR_FIFO); |
|
} |
|
|
|
/* I2C target reg access */ |
|
#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; |
|
} |
|
} |
|
#endif /* CONFIG_I2C_TARGET */ |
|
|
|
/* |
|
* I2C local functions which touch the registers in 'Normal' bank. These |
|
* utilities will change bank back to FIFO mode when leaving themselves in case |
|
* the other utilities access the registers in 'FIFO' bank. |
|
*/ |
|
static void i2c_ctrl_hold_bus(const struct device *dev, int stall) |
|
{ |
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_NORMAL); |
|
|
|
if (stall) { |
|
i2c_ctrl_norm_stall_scl(dev); |
|
} else { |
|
i2c_ctrl_norm_free_scl(dev); |
|
} |
|
|
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_FIFO); |
|
} |
|
|
|
static void i2c_ctrl_init_module(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_NORMAL); |
|
|
|
/* Enable FIFO mode first */ |
|
inst->SMBFIF_CTL |= BIT(NPCX_SMBFIF_CTL_FIFO_EN); |
|
|
|
/* Enable module - before configuring CTL1 */ |
|
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE); |
|
|
|
#ifdef CONFIG_I2C_TARGET |
|
volatile uint8_t *reg_smbaddr; |
|
|
|
/* Clear all the SMBnADDR */ |
|
for (int i = 0; i < NPCX_I2C_FLAG_COUNT; i++) { |
|
reg_smbaddr = npcx_i2c_ctrl_target_get_reg_smbaddr(dev, i); |
|
*reg_smbaddr = 0; |
|
} |
|
#endif |
|
|
|
/* Enable SMB interrupt and 'New Address Match' interrupt source */ |
|
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN); |
|
|
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_FIFO); |
|
} |
|
|
|
static void i2c_ctrl_config_bus_freq(const struct device *dev, |
|
enum npcx_i2c_freq bus_freq) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
struct i2c_ctrl_data *const data = dev->data; |
|
const struct npcx_i2c_timing_cfg bus_cfg = |
|
data->ptr_speed_confs[bus_freq]; |
|
|
|
/* Switch to bank 0 to configure bus speed */ |
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_NORMAL); |
|
|
|
/* Configure bus speed */ |
|
if (bus_freq == NPCX_I2C_BUS_SPEED_100KHZ) { |
|
/* Enable 'Normal' Mode */ |
|
inst->SMBCTL3 &= ~(BIT(NPCX_SMBCTL3_400K)); |
|
/* Set freq of SCL. For 100KHz, only k1 is used. */ |
|
SET_FIELD(inst->SMBCTL2, NPCX_SMBCTL2_SCLFRQ0_6_FIELD, |
|
bus_cfg.k1/2 & 0x7f); |
|
SET_FIELD(inst->SMBCTL3, NPCX_SMBCTL3_SCLFRQ7_8_FIELD, |
|
bus_cfg.k1/2 >> 7); |
|
SET_FIELD(inst->SMBCTL4, NPCX_SMBCTL4_HLDT_FIELD, |
|
bus_cfg.HLDT); |
|
} else { |
|
/* Enable 'Fast' Mode for 400K or higher freq. */ |
|
inst->SMBCTL3 |= BIT(NPCX_SMBCTL3_400K); |
|
/* Set high/low time of SCL and hold-time */ |
|
inst->SMBSCLLT = bus_cfg.k1/2; |
|
inst->SMBSCLHT = bus_cfg.k2/2; |
|
SET_FIELD(inst->SMBCTL4, NPCX_SMBCTL4_HLDT_FIELD, |
|
bus_cfg.HLDT); |
|
} |
|
|
|
/* Switch to bank 1 to access I2C FIFO registers */ |
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_FIFO); |
|
} |
|
|
|
/* I2C controller local functions */ |
|
static int i2c_ctrl_wait_stop_completed(const struct device *dev, int timeout) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
if (timeout <= 0) { |
|
return -EINVAL; |
|
} |
|
|
|
do { |
|
/* |
|
* Wait till i2c bus is idle. This bit is cleared to 0 |
|
* automatically after the STOP condition is generated. |
|
*/ |
|
if (!IS_BIT_SET(inst->SMBCTL1, NPCX_SMBCTL1_STOP)) { |
|
break; |
|
} |
|
k_msleep(1); |
|
} while (--timeout); |
|
|
|
if (timeout > 0) { |
|
return 0; |
|
} else { |
|
return -ETIMEDOUT; |
|
} |
|
} |
|
|
|
static bool i2c_ctrl_is_scl_sda_both_high(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
|
|
if (IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SCL_LVL) && |
|
IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SDA_LVL)) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static int i2c_ctrl_wait_idle_completed(const struct device *dev, int timeout) |
|
{ |
|
if (timeout <= 0) { |
|
return -EINVAL; |
|
} |
|
|
|
do { |
|
/* Wait for both SCL & SDA lines are high */ |
|
if (i2c_ctrl_is_scl_sda_both_high(dev)) { |
|
break; |
|
} |
|
k_msleep(1); |
|
} while (--timeout); |
|
|
|
if (timeout > 0) { |
|
return 0; |
|
} else { |
|
return -ETIMEDOUT; |
|
} |
|
} |
|
|
|
static int i2c_ctrl_recovery(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
struct i2c_ctrl_data *const data = dev->data; |
|
int ret; |
|
|
|
if (data->oper_state != NPCX_I2C_ERROR_RECOVERY) { |
|
data->oper_state = NPCX_I2C_ERROR_RECOVERY; |
|
} |
|
|
|
/* Step 1: Make sure the bus is not stalled before exit. */ |
|
i2c_ctrl_hold_bus(dev, 0); |
|
|
|
/* |
|
* Step 2: Abort data, wait for STOP condition completed. |
|
* - Clearing NEGACK and BER bits first |
|
* - Wait for STOP condition completed |
|
* - Then clear BB (BUS BUSY) bit |
|
*/ |
|
inst->SMBST = BIT(NPCX_SMBST_BER) | BIT(NPCX_SMBST_NEGACK); |
|
ret = i2c_ctrl_wait_stop_completed(dev, I2C_MAX_TIMEOUT); |
|
inst->SMBCST |= BIT(NPCX_SMBCST_BB); |
|
if (ret != 0) { |
|
LOG_ERR("Abort i2c %s::%02x fail! Bus might be stalled.", dev->name, data->port); |
|
} |
|
|
|
/* |
|
* Step 3: Reset i2c module to clear all internal state machine of it |
|
* - Disable the SMB module first |
|
* - Wait both SCL/SDA line are high |
|
* - Enable i2c module again |
|
*/ |
|
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE); |
|
ret = i2c_ctrl_wait_idle_completed(dev, I2C_MAX_TIMEOUT); |
|
if (ret != 0) { |
|
LOG_ERR("Reset i2c %s::%02x fail! Bus might be stalled.", dev->name, data->port); |
|
return -EIO; |
|
} |
|
|
|
/* Reset module and internal state machine */ |
|
i2c_ctrl_init_module(dev); |
|
|
|
/* Recovery is completed */ |
|
data->oper_state = NPCX_I2C_IDLE; |
|
return 0; |
|
} |
|
|
|
static void i2c_ctrl_notify(const struct device *dev, int error) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
data->trans_err = error; |
|
k_sem_give(&data->sync_sem); |
|
} |
|
|
|
static int i2c_ctrl_wait_completion(const struct device *dev) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
if (k_sem_take(&data->sync_sem, I2C_TRANS_TIMEOUT) == 0) { |
|
return data->trans_err; |
|
} else { |
|
return -ETIMEDOUT; |
|
} |
|
} |
|
|
|
size_t i2c_ctrl_calculate_msg_remains(const struct device *dev) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
uint8_t *buf_end = data->msg->buf + data->msg->len; |
|
|
|
return (buf_end > data->ptr_msg) ? (buf_end - data->ptr_msg) : 0; |
|
} |
|
|
|
static void i2c_ctrl_handle_write_int_event(const struct device *dev) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
/* START condition is issued */ |
|
if (data->oper_state == NPCX_I2C_WAIT_START) { |
|
/* Write slave address with W bit */ |
|
i2c_ctrl_fifo_write(dev, ((data->addr << 1) & ~BIT(0))); |
|
/* Start to proceed write process */ |
|
data->oper_state = NPCX_I2C_WRITE_FIFO; |
|
return; |
|
} |
|
|
|
/* Write message data bytes to FIFO */ |
|
if (data->oper_state == NPCX_I2C_WRITE_FIFO) { |
|
/* Calculate how many remaining bytes need to transmit */ |
|
size_t tx_remain = i2c_ctrl_calculate_msg_remains(dev); |
|
size_t tx_avail = MIN(tx_remain, i2c_ctrl_fifo_tx_avail(dev)); |
|
|
|
for (int i = 0U; i < tx_avail; i++) { |
|
i2c_ctrl_fifo_write(dev, *(data->ptr_msg++)); |
|
} |
|
|
|
/* Is there any remaining bytes? */ |
|
if (data->ptr_msg == data->msg->buf + data->msg->len) { |
|
data->oper_state = NPCX_I2C_WRITE_SUSPEND; |
|
} |
|
return; |
|
} |
|
|
|
/* Issue STOP after sending message? */ |
|
if (data->oper_state == NPCX_I2C_WRITE_SUSPEND) { |
|
if (data->msg->flags & I2C_MSG_STOP) { |
|
/* Generate a STOP condition immediately */ |
|
i2c_ctrl_stop(dev); |
|
/* Clear rx FIFO threshold and status bits */ |
|
i2c_ctrl_fifo_clear_status(dev); |
|
/* Wait for STOP completed */ |
|
data->oper_state = NPCX_I2C_WAIT_STOP; |
|
} else { |
|
uint8_t next_msg_idx = data->msg_curr_idx + 1; |
|
|
|
if (next_msg_idx < data->msg_max_num) { |
|
struct i2c_msg *msg; |
|
|
|
data->msg_curr_idx = next_msg_idx; |
|
msg = data->msg_head + next_msg_idx; |
|
data->msg = msg; |
|
data->ptr_msg = msg->buf; |
|
if ((msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { |
|
data->oper_state = NPCX_I2C_WRITE_FIFO; |
|
} else { |
|
data->is_write = 0; |
|
data->oper_state = NPCX_I2C_WAIT_RESTART; |
|
i2c_ctrl_start(dev); |
|
} |
|
return; |
|
} |
|
/* Disable interrupt and handle next message */ |
|
i2c_ctrl_irq_enable(dev, 0); |
|
} |
|
} |
|
|
|
i2c_ctrl_notify(dev, 0); |
|
} |
|
|
|
static void i2c_ctrl_handle_read_int_event(const struct device *dev) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
/* START or RESTART condition is issued */ |
|
if (data->oper_state == NPCX_I2C_WAIT_START || |
|
data->oper_state == NPCX_I2C_WAIT_RESTART) { |
|
/* Setup threshold of rx FIFO before sending address byte */ |
|
i2c_ctrl_fifo_rx_setup_threshold_nack(dev, data->msg->len, |
|
(data->msg->flags & I2C_MSG_STOP) != 0); |
|
/* Write slave address with R bit */ |
|
i2c_ctrl_fifo_write(dev, ((data->addr << 1) | BIT(0))); |
|
/* Start to proceed read process */ |
|
data->oper_state = NPCX_I2C_READ_FIFO; |
|
return; |
|
} |
|
|
|
/* Read message data bytes from FIFO */ |
|
if (data->oper_state == NPCX_I2C_READ_FIFO) { |
|
/* Calculate how many remaining bytes need to receive */ |
|
size_t rx_remain = i2c_ctrl_calculate_msg_remains(dev); |
|
size_t rx_occupied = i2c_ctrl_fifo_rx_occupied(dev); |
|
|
|
/* Is it the last read transaction with STOP condition? */ |
|
if (rx_occupied >= rx_remain && |
|
(data->msg->flags & I2C_MSG_STOP) != 0) { |
|
/* |
|
* Generate a STOP condition before reading data bytes |
|
* from FIFO. It prevents a glitch on SCL. |
|
*/ |
|
i2c_ctrl_stop(dev); |
|
} else { |
|
/* |
|
* Hold SCL line here in case the hardware releases bus |
|
* immediately after the driver start to read data from |
|
* FIFO. Then we might lose incoming data from device. |
|
*/ |
|
i2c_ctrl_hold_bus(dev, 1); |
|
} |
|
|
|
/* Read data bytes from FIFO */ |
|
for (int i = 0; i < rx_occupied; i++) { |
|
*(data->ptr_msg++) = i2c_ctrl_fifo_read(dev); |
|
} |
|
rx_remain = i2c_ctrl_calculate_msg_remains(dev); |
|
|
|
/* Setup threshold of RX FIFO if needed */ |
|
if (rx_remain > 0) { |
|
i2c_ctrl_fifo_rx_setup_threshold_nack(dev, rx_remain, |
|
(data->msg->flags & I2C_MSG_STOP) != 0); |
|
/* Release bus */ |
|
i2c_ctrl_hold_bus(dev, 0); |
|
return; |
|
} else if ((data->msg->flags & I2C_MSG_STOP) == 0) { |
|
uint8_t next_msg_idx = data->msg_curr_idx + 1; |
|
|
|
if (next_msg_idx < data->msg_max_num) { |
|
struct i2c_msg *msg; |
|
|
|
msg = data->msg_head + next_msg_idx; |
|
if ((msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { |
|
|
|
data->msg_curr_idx = next_msg_idx; |
|
data->msg = msg; |
|
data->ptr_msg = msg->buf; |
|
|
|
/* Setup threshold of RX FIFO first */ |
|
i2c_ctrl_fifo_rx_setup_threshold_nack( |
|
dev, msg->len, (msg->flags & I2C_MSG_STOP) != 0); |
|
/* Release bus */ |
|
i2c_ctrl_hold_bus(dev, 0); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* Is the STOP condition issued? */ |
|
if (data->msg != NULL && (data->msg->flags & I2C_MSG_STOP) != 0) { |
|
/* Clear rx FIFO threshold and status bits */ |
|
i2c_ctrl_fifo_clear_status(dev); |
|
|
|
/* Wait for STOP completed */ |
|
data->oper_state = NPCX_I2C_WAIT_STOP; |
|
} else { |
|
/* Disable i2c interrupt first */ |
|
i2c_ctrl_irq_enable(dev, 0); |
|
data->oper_state = NPCX_I2C_READ_SUSPEND; |
|
} |
|
|
|
i2c_ctrl_notify(dev, 0); |
|
} |
|
|
|
static int i2c_ctrl_proc_write_msg(const struct device *dev, |
|
struct i2c_msg *msg) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
data->is_write = 1; |
|
data->ptr_msg = msg->buf; |
|
data->msg = msg; |
|
|
|
if (data->oper_state == NPCX_I2C_IDLE) { |
|
data->oper_state = NPCX_I2C_WAIT_START; |
|
|
|
/* Clear FIFO status before starting a new transaction */ |
|
i2c_ctrl_fifo_clear_status(dev); |
|
|
|
/* Issue a START, wait for transaction completed */ |
|
i2c_ctrl_start(dev); |
|
|
|
return i2c_ctrl_wait_completion(dev); |
|
} else if (data->oper_state == NPCX_I2C_WRITE_SUSPEND) { |
|
data->oper_state = NPCX_I2C_WRITE_FIFO; |
|
i2c_ctrl_irq_enable(dev, 1); |
|
|
|
return i2c_ctrl_wait_completion(dev); |
|
} |
|
|
|
LOG_ERR("Unexpected state %d during writing i2c port%02x!", |
|
data->oper_state, data->port); |
|
data->trans_err = -EIO; |
|
return data->trans_err; |
|
} |
|
|
|
static int i2c_ctrl_proc_read_msg(const struct device *dev, struct i2c_msg *msg) |
|
{ |
|
struct i2c_ctrl_data *const data = dev->data; |
|
|
|
data->is_write = 0; |
|
data->ptr_msg = msg->buf; |
|
data->msg = msg; |
|
|
|
if (data->oper_state == NPCX_I2C_IDLE) { |
|
data->oper_state = NPCX_I2C_WAIT_START; |
|
|
|
/* Clear FIFO status before starting a new transaction */ |
|
i2c_ctrl_fifo_clear_status(dev); |
|
|
|
/* Issue a START, wait for transaction completed */ |
|
i2c_ctrl_start(dev); |
|
|
|
return i2c_ctrl_wait_completion(dev); |
|
} else if (data->oper_state == NPCX_I2C_WRITE_SUSPEND) { |
|
data->oper_state = NPCX_I2C_WAIT_RESTART; |
|
/* Issue a RESTART, wait for transaction completed */ |
|
i2c_ctrl_start(dev); |
|
i2c_ctrl_irq_enable(dev, 1); |
|
|
|
return i2c_ctrl_wait_completion(dev); |
|
} else if (data->oper_state == NPCX_I2C_READ_SUSPEND) { |
|
data->oper_state = NPCX_I2C_READ_FIFO; |
|
|
|
/* Setup threshold of RX FIFO first */ |
|
i2c_ctrl_fifo_rx_setup_threshold_nack(dev, msg->len, |
|
(msg->flags & I2C_MSG_STOP) != 0); |
|
|
|
/* Release bus */ |
|
i2c_ctrl_hold_bus(dev, 0); |
|
|
|
/* Enable i2c interrupt first */ |
|
i2c_ctrl_irq_enable(dev, 1); |
|
return i2c_ctrl_wait_completion(dev); |
|
} |
|
|
|
LOG_ERR("Unexpected state %d during reading i2c port%02x!", |
|
data->oper_state, data->port); |
|
data->trans_err = -EIO; |
|
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 = 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 != NULL) && target_cb->stop) { |
|
target_cb->stop(data->target_cfg[data->target_idx]); |
|
} |
|
|
|
/* Reset i2c module in target mode */ |
|
inst->SMBCTL2 &= ~BIT(NPCX_SMBCTL2_ENABLE); |
|
inst->SMBCTL2 |= BIT(NPCX_SMBCTL2_ENABLE); |
|
|
|
/* |
|
* Re-enable interrupts because they are turned off after the SMBus module |
|
* is reset above. |
|
*/ |
|
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN); |
|
/* End of transaction */ |
|
data->oper_state = NPCX_I2C_IDLE; |
|
|
|
#ifdef CONFIG_PM |
|
i2c_npcx_pm_policy_state_lock_put(dev, I2C_PM_POLICY_STATE_FLAG_TGT); |
|
#endif /* CONFIG_PM */ |
|
|
|
LOG_DBG("target: Bus error on port%02x!", data->port); |
|
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 */ |
|
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 |
|
i2c_npcx_pm_policy_state_lock_put(dev, I2C_PM_POLICY_STATE_FLAG_TGT); |
|
#endif /* CONFIG_PM */ |
|
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); |
|
|
|
/* 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 != 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 != 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 != 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 != NULL) && target_cb->write_received) { |
|
val = inst->SMBSDA; |
|
/* Notify upper layer one byte received */ |
|
target_cb->write_received(data->target_cfg[data->target_idx], 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 /* CONFIG_I2C_TARGET */ |
|
|
|
/* I2C controller isr function */ |
|
static void i2c_ctrl_isr(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
struct i2c_ctrl_data *const data = dev->data; |
|
uint8_t status, tmp; |
|
|
|
status = inst->SMBST & NPCX_VALID_SMBST_MASK; |
|
|
|
#ifdef CONFIG_I2C_TARGET |
|
if (atomic_get(&data->registered_target_mask) != (atomic_val_t) 0) { |
|
i2c_ctrl_target_isr(dev, status); |
|
return; |
|
} |
|
#endif /* CONFIG_I2C_TARGET */ |
|
|
|
/* A 'Bus Error' has been identified */ |
|
if (IS_BIT_SET(status, NPCX_SMBST_BER)) { |
|
/* Generate a STOP condition immediately */ |
|
i2c_ctrl_stop(dev); |
|
|
|
/* Clear BER Bit */ |
|
inst->SMBST = BIT(NPCX_SMBST_BER); |
|
|
|
/* Make sure slave doesn't hold bus by reading FIFO again */ |
|
tmp = i2c_ctrl_fifo_read(dev); |
|
|
|
LOG_ERR("Bus error occurred on i2c %s::%02x!", dev->name, data->port); |
|
data->oper_state = NPCX_I2C_ERROR_RECOVERY; |
|
|
|
/* I/O error occurred */ |
|
i2c_ctrl_notify(dev, -EIO); |
|
return; |
|
} |
|
|
|
/* A negative acknowledge has occurred */ |
|
if (IS_BIT_SET(status, NPCX_SMBST_NEGACK)) { |
|
/* Generate a STOP condition immediately */ |
|
i2c_ctrl_stop(dev); |
|
|
|
/* Clear NEGACK Bit */ |
|
inst->SMBST = BIT(NPCX_SMBST_NEGACK); |
|
|
|
/* End transaction */ |
|
data->oper_state = NPCX_I2C_WAIT_STOP; |
|
|
|
/* No such device or address */ |
|
i2c_ctrl_notify(dev, -ENXIO); |
|
return; |
|
} |
|
|
|
/* START, tx FIFO empty or rx FIFO full has occurred */ |
|
if (IS_BIT_SET(status, NPCX_SMBST_SDAST)) { |
|
if (data->is_write) { |
|
i2c_ctrl_handle_write_int_event(dev); |
|
} else { |
|
i2c_ctrl_handle_read_int_event(dev); |
|
} |
|
return; |
|
} |
|
|
|
/* Clear unexpected status bits */ |
|
if (status != 0) { |
|
inst->SMBST = status; |
|
LOG_ERR("Unexpected SMBST 0x%02x occurred on i2c port%02x!", |
|
status, data->port); |
|
} |
|
} |
|
|
|
/* NPCX specific I2C controller functions */ |
|
void npcx_i2c_ctrl_mutex_lock(const struct device *i2c_dev) |
|
{ |
|
struct i2c_ctrl_data *const data = i2c_dev->data; |
|
|
|
k_sem_take(&data->lock_sem, K_FOREVER); |
|
} |
|
|
|
void npcx_i2c_ctrl_mutex_unlock(const struct device *i2c_dev) |
|
{ |
|
struct i2c_ctrl_data *const data = i2c_dev->data; |
|
|
|
k_sem_give(&data->lock_sem); |
|
} |
|
|
|
int npcx_i2c_ctrl_configure(const struct device *i2c_dev, uint32_t dev_config) |
|
{ |
|
struct i2c_ctrl_data *const data = i2c_dev->data; |
|
|
|
switch (I2C_SPEED_GET(dev_config)) { |
|
case I2C_SPEED_STANDARD: |
|
data->bus_freq = NPCX_I2C_BUS_SPEED_100KHZ; |
|
break; |
|
case I2C_SPEED_FAST: |
|
data->bus_freq = NPCX_I2C_BUS_SPEED_400KHZ; |
|
break; |
|
case I2C_SPEED_FAST_PLUS: |
|
data->bus_freq = NPCX_I2C_BUS_SPEED_1MHZ; |
|
break; |
|
default: |
|
return -ERANGE; |
|
} |
|
|
|
i2c_ctrl_config_bus_freq(i2c_dev, data->bus_freq); |
|
data->is_configured = true; |
|
|
|
return 0; |
|
} |
|
|
|
int npcx_i2c_ctrl_get_speed(const struct device *i2c_dev, uint32_t *speed) |
|
{ |
|
struct i2c_ctrl_data *const data = i2c_dev->data; |
|
|
|
if (!data->is_configured) { |
|
return -EIO; |
|
} |
|
|
|
switch (data->bus_freq) { |
|
case NPCX_I2C_BUS_SPEED_100KHZ: |
|
*speed = I2C_SPEED_SET(I2C_SPEED_STANDARD); |
|
break; |
|
case NPCX_I2C_BUS_SPEED_400KHZ: |
|
*speed = I2C_SPEED_SET(I2C_SPEED_FAST); |
|
break; |
|
case NPCX_I2C_BUS_SPEED_1MHZ: |
|
*speed = I2C_SPEED_SET(I2C_SPEED_FAST_PLUS); |
|
break; |
|
default: |
|
return -ERANGE; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int npcx_i2c_ctrl_recover_bus(const struct device *dev) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev); |
|
int ret = 0; |
|
|
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_NORMAL); |
|
|
|
/* |
|
* When the SCL is low, wait for a while in case of the clock is stalled |
|
* by a I2C target. |
|
*/ |
|
if (!IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SCL_LVL)) { |
|
for (int i = 0;; i++) { |
|
if (i >= I2C_RECOVER_SCL_RETRY) { |
|
ret = -EBUSY; |
|
goto recover_exit; |
|
} |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
if (IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SCL_LVL)) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SDA_LVL)) { |
|
goto recover_exit; |
|
} |
|
|
|
for (int i = 0; i < I2C_RECOVER_SDA_RETRY; i++) { |
|
/* Drive the clock high. */ |
|
i2c_ctrl_norm_free_scl(dev); |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
|
|
/* |
|
* Toggle SCL to generate 9 clocks. If the I2C target releases the SDA, we can stop |
|
* toggle the SCL and issue a STOP. |
|
*/ |
|
for (int j = 0; j < 9; j++) { |
|
if (IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SDA_LVL)) { |
|
break; |
|
} |
|
|
|
i2c_ctrl_norm_stall_scl(dev); |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
i2c_ctrl_norm_free_scl(dev); |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
} |
|
|
|
/* Drive the SDA line to issue STOP. */ |
|
i2c_ctrl_norm_stall_sda(dev); |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
i2c_ctrl_norm_free_sda(dev); |
|
k_busy_wait(I2C_RECOVER_BUS_DELAY_US); |
|
|
|
if (i2c_ctrl_is_scl_sda_both_high(dev)) { |
|
ret = 0; |
|
goto recover_exit; |
|
} |
|
} |
|
|
|
if (!IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SDA_LVL)) { |
|
LOG_ERR("Recover SDA fail"); |
|
ret = -EBUSY; |
|
} |
|
if (!IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SCL_LVL)) { |
|
LOG_ERR("Recover SCL fail"); |
|
ret = -EBUSY; |
|
} |
|
|
|
recover_exit: |
|
i2c_ctrl_bank_sel(dev, NPCX_I2C_BANK_FIFO); |
|
|
|
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); |
|
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 idx_port = (port & 0x0F); |
|
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 && data->oper_state != NPCX_I2C_ERROR_RECOVERY) { |
|
LOG_ERR("Reg TGT in err state: %d", data->oper_state); |
|
return -EBUSY; |
|
} |
|
if (data->oper_state == NPCX_I2C_ERROR_RECOVERY) { |
|
LOG_WRN("Reg TGT in Recovery"); |
|
} |
|
|
|
/* 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 */ |
|
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); |
|
|
|
/* Reconfigure SMBCTL1 */ |
|
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN); |
|
|
|
/* Enable irq of smb wake-up event */ |
|
if (IS_ENABLED(CONFIG_PM) && config->wakeup_source) { |
|
|
|
/* Enable SMB wake up detection */ |
|
npcx_i2c_target_start_wk_enable(idx_ctrl, true); |
|
/* Enable start detect in IDLE */ |
|
inst->SMBCTL3 |= BIT(NPCX_SMBCTL3_IDL_START); |
|
/* Enable SMB's MIWU interrupts */ |
|
npcx_miwu_irq_enable(&config->smb_wui); |
|
} |
|
|
|
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, uint8_t port) |
|
{ |
|
struct smb_reg *const inst = HAL_I2C_INSTANCE(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_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; |
|
} |
|
|
|
/* 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); |
|
|
|
*reg_smbaddr = 0; /* Disable target mode and clear address setting */ |
|
data->target_cfg[cur_addr_slot] = NULL; /* Clear target config */ |
|
|
|
/* Mark it as controller mode */ |
|
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_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; |
|
} |
|
|
|
static void i2c_target_wk_isr(const struct device *dev, struct npcx_wui *wui) |
|
{ |
|
/* Clear wake up detection event status */ |
|
npcx_i2c_target_clear_detection_event(); |
|
|
|
/* |
|
* Suspend-to-idle stops SMB module clocks (derived from APB2/APB3), which must remain |
|
* active during a transaction. |
|
* |
|
* This also prevent Sr set pm_policy_state_lock_get() twice. |
|
* Otherwise, it will cause I2C cannot switch to deep sleep state for the next time. |
|
*/ |
|
#ifdef CONFIG_PM |
|
i2c_npcx_pm_policy_state_lock_get(dev, I2C_PM_POLICY_STATE_FLAG_TGT); |
|
#endif /* CONFIG_PM */ |
|
} |
|
#endif /* CONFIG_I2C_TARGET */ |
|
|
|
int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs, |
|
uint8_t num_msgs, uint16_t addr, uint8_t port) |
|
{ |
|
struct i2c_ctrl_data *const data = i2c_dev->data; |
|
int ret = 0; |
|
struct i2c_msg *msg = msgs; |
|
|
|
#ifdef CONFIG_I2C_TARGET |
|
/* I2c module has been configured to target mode */ |
|
if (atomic_get(&data->registered_target_mask) != (atomic_val_t) 0) { |
|
return -EBUSY; |
|
} |
|
#endif /* CONFIG_I2C_TARGET */ |
|
|
|
/* |
|
* suspend-to-idle stops SMB module clocks (derived from APB2/APB3), which must remain |
|
* active during a transaction |
|
*/ |
|
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
|
|
|
/* Does bus need recovery? */ |
|
if (data->oper_state != NPCX_I2C_WRITE_SUSPEND && |
|
data->oper_state != NPCX_I2C_READ_SUSPEND) { |
|
if (i2c_ctrl_bus_busy(i2c_dev) || !i2c_ctrl_is_scl_sda_both_high(i2c_dev) || |
|
data->oper_state == NPCX_I2C_ERROR_RECOVERY) { |
|
ret = npcx_i2c_ctrl_recover_bus(i2c_dev); |
|
if (ret != 0) { |
|
LOG_ERR("Recover Bus failed: %s::%d", i2c_dev->name, port); |
|
goto out; |
|
} |
|
|
|
ret = i2c_ctrl_recovery(i2c_dev); |
|
/* Recovery failed, return it immediately */ |
|
if (ret) { |
|
goto out; |
|
} |
|
} |
|
} |
|
|
|
/* Start i2c transaction */ |
|
data->port = port; |
|
data->trans_err = 0; |
|
data->addr = addr; |
|
|
|
data->msg_head = msgs; |
|
data->msg_max_num = num_msgs; |
|
data->msg_curr_idx = 0; |
|
|
|
/* |
|
* Reset i2c event-completed semaphore before starting transactions. |
|
* Some interrupt events such as BUS_ERROR might change its counter |
|
* when bus is idle. |
|
*/ |
|
k_sem_reset(&data->sync_sem); |
|
|
|
if ((msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { |
|
ret = i2c_ctrl_proc_write_msg(i2c_dev, msg); |
|
} else { /* Handle read transaction */ |
|
ret = i2c_ctrl_proc_read_msg(i2c_dev, msg); |
|
} |
|
|
|
/* Check STOP completed? */ |
|
if (data->oper_state == NPCX_I2C_WAIT_STOP) { |
|
data->trans_err = i2c_ctrl_wait_stop_completed(i2c_dev, |
|
I2C_MIN_TIMEOUT); |
|
if (data->trans_err == 0) { |
|
data->oper_state = NPCX_I2C_IDLE; |
|
} else { |
|
LOG_ERR("STOP fail! bus is held on i2c %s::%02x!\n", i2c_dev->name, |
|
data->port); |
|
data->oper_state = NPCX_I2C_ERROR_RECOVERY; |
|
} |
|
} |
|
|
|
if (data->oper_state == NPCX_I2C_ERROR_RECOVERY || ret == -ETIMEDOUT) { |
|
int recovery_error = i2c_ctrl_recovery(i2c_dev); |
|
/* |
|
* Recovery failed, return it immediately. Otherwise, the upper |
|
* layer still needs to know why the transaction failed. |
|
*/ |
|
if (recovery_error != 0) { |
|
ret = recovery_error; |
|
} |
|
} |
|
|
|
out: |
|
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
|
return ret; |
|
} |
|
|
|
/* I2C controller driver registration */ |
|
static int i2c_ctrl_init(const struct device *dev) |
|
{ |
|
const struct i2c_ctrl_config *const config = dev->config; |
|
struct i2c_ctrl_data *const data = dev->data; |
|
const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); |
|
uint32_t i2c_rate; |
|
|
|
if (!device_is_ready(clk_dev)) { |
|
LOG_ERR("clock control device not ready"); |
|
return -ENODEV; |
|
} |
|
|
|
/* Turn on device clock first and get source clock freq. */ |
|
if (clock_control_on(clk_dev, |
|
(clock_control_subsys_t) &config->clk_cfg) != 0) { |
|
LOG_ERR("Turn on %s clock fail.", dev->name); |
|
return -EIO; |
|
} |
|
|
|
/* |
|
* If apb2/3's clock is not 15MHz, we need to add the other timing |
|
* configuration of the device to meet SMBus timing spec. Please refer |
|
* Table 21/22/23 and section 7.5.9 SMBus Timing for more detail. |
|
*/ |
|
if (clock_control_get_rate(clk_dev, (clock_control_subsys_t) |
|
&config->clk_cfg, &i2c_rate) != 0) { |
|
LOG_ERR("Get %s clock rate error.", dev->name); |
|
return -EIO; |
|
} |
|
|
|
if (i2c_rate == 15000000) { |
|
data->ptr_speed_confs = npcx_15m_speed_confs; |
|
} else if (i2c_rate == 20000000) { |
|
data->ptr_speed_confs = npcx_20m_speed_confs; |
|
} else { |
|
LOG_ERR("Unsupported apb2/3 freq for %s.", dev->name); |
|
return -EIO; |
|
} |
|
|
|
/* Initialize i2c module */ |
|
i2c_ctrl_init_module(dev); |
|
|
|
#ifdef CONFIG_I2C_TARGET |
|
if (IS_ENABLED(CONFIG_PM) && config->wakeup_source) { |
|
/* Initialize a miwu device input and its callback function */ |
|
npcx_miwu_init_dev_callback(&data->smb_wk_cb, &config->smb_wui, |
|
i2c_target_wk_isr, dev); |
|
npcx_miwu_manage_callback(&data->smb_wk_cb, true); |
|
/* |
|
* Configure Start condition wake-up configuration of SMB |
|
* controller. |
|
*/ |
|
npcx_miwu_interrupt_configure(&config->smb_wui, NPCX_MIWU_MODE_EDGE, |
|
NPCX_MIWU_TRIG_HIGH); |
|
} |
|
#endif /* CONFIG_I2C_TARGET */ |
|
|
|
/* initialize mutex and semaphore for i2c/smb controller */ |
|
k_sem_init(&data->lock_sem, 1, 1); |
|
k_sem_init(&data->sync_sem, 0, K_SEM_MAX_LIMIT); |
|
|
|
/* Initialize driver status machine */ |
|
data->oper_state = NPCX_I2C_IDLE; |
|
|
|
return 0; |
|
} |
|
|
|
/* I2C controller init macro functions */ |
|
#define NPCX_I2C_CTRL_INIT_FUNC(inst) _CONCAT(i2c_ctrl_init_, inst) |
|
#define NPCX_I2C_CTRL_INIT_FUNC_DECL(inst) \ |
|
static int i2c_ctrl_init_##inst(const struct device *dev) |
|
#define NPCX_I2C_CTRL_INIT_FUNC_IMPL(inst) \ |
|
static int i2c_ctrl_init_##inst(const struct device *dev) \ |
|
{ \ |
|
int ret; \ |
|
\ |
|
ret = i2c_ctrl_init(dev); \ |
|
IRQ_CONNECT(DT_INST_IRQN(inst), \ |
|
DT_INST_IRQ(inst, priority), \ |
|
i2c_ctrl_isr, \ |
|
DEVICE_DT_INST_GET(inst), \ |
|
0); \ |
|
irq_enable(DT_INST_IRQN(inst)); \ |
|
\ |
|
return ret; \ |
|
} |
|
|
|
|
|
#define NPCX_I2C_CTRL_INIT(inst) \ |
|
NPCX_I2C_CTRL_INIT_FUNC_DECL(inst); \ |
|
\ |
|
static const struct i2c_ctrl_config i2c_ctrl_cfg_##inst = { \ |
|
.base = DT_INST_REG_ADDR(inst), \ |
|
.irq = DT_INST_IRQN(inst), \ |
|
.clk_cfg = NPCX_DT_CLK_CFG_ITEM(inst), \ |
|
IF_ENABLED(CONFIG_I2C_TARGET, ( \ |
|
.smb_wui = NPCX_DT_WUI_ITEM_BY_NAME(inst, smb_wui), \ |
|
.wakeup_source = DT_INST_PROP_OR(inst, wakeup_source, 0) \ |
|
)) \ |
|
}; \ |
|
\ |
|
static struct i2c_ctrl_data i2c_ctrl_data_##inst; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(inst, \ |
|
NPCX_I2C_CTRL_INIT_FUNC(inst), \ |
|
NULL, \ |
|
&i2c_ctrl_data_##inst, &i2c_ctrl_cfg_##inst, \ |
|
PRE_KERNEL_1, CONFIG_I2C_INIT_PRIORITY, \ |
|
NULL); \ |
|
\ |
|
NPCX_I2C_CTRL_INIT_FUNC_IMPL(inst) |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(NPCX_I2C_CTRL_INIT)
|
|
|