1340 lines
39 KiB
C
1340 lines
39 KiB
C
/*
|
|
* 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 <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(i2c_npcx, LOG_LEVEL_ERR);
|
|
|
|
/* 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
|
|
|
|
/* 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_TARGET,
|
|
NPCX_I2C_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 */
|
|
};
|
|
|
|
/* 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 */
|
|
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 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 */
|
|
#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},
|
|
};
|
|
|
|
/* 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 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);
|
|
|
|
/* 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 port%02x fail! Bus might be stalled.",
|
|
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 port%02x fail! Bus might be stalled.",
|
|
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));
|
|
|
|
LOG_DBG("tx remains %d, avail %d", tx_remain, tx_avail);
|
|
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 {
|
|
/* 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);
|
|
|
|
LOG_DBG("rx remains %d, occupied %d", rx_remain, rx_occupied);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/* Is the STOP condition issued? */
|
|
if ((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 = 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);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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 */
|
|
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 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);
|
|
}
|
|
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)
|
|
{
|
|
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;
|
|
LOG_DBG("status: %02x, %d", status, data->oper_state);
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
|
|
i2c_ctrl_target_isr(dev, status);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* 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 port%02x!", 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);
|
|
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)
|
|
{
|
|
struct i2c_ctrl_data *const data = i2c_dev->data;
|
|
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
|
|
*/
|
|
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");
|
|
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;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
for (i = 0U; i < num_msgs; i++) {
|
|
struct i2c_msg *msg = msgs + i;
|
|
|
|
/* Handle write transaction */
|
|
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);
|
|
}
|
|
if (ret < 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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 port%02x!",
|
|
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);
|
|
|
|
/* 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), \
|
|
}; \
|
|
\
|
|
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)
|