drivers: i2c: npcx: add support to wake up from sleep mode

Add support to wake up from sleep mode by START condition when i2c
is configured to target mode.

Signed-off-by: Alvis Sun <yfsun@nuvoton.com>
Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
This commit is contained in:
Mulin Chao 2024-06-05 17:51:17 +08:00 committed by Benjamin Cabé
commit 47f472aa5c
5 changed files with 195 additions and 27 deletions

View file

@ -73,6 +73,9 @@
#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>
@ -117,6 +120,11 @@ enum npcx_i2c_flag {
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
@ -145,6 +153,11 @@ 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 */
@ -164,7 +177,13 @@ struct i2c_ctrl_data {
#ifdef CONFIG_I2C_TARGET
struct i2c_target_config *target_cfg;
atomic_t flags;
#endif
/* 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 */
@ -184,6 +203,38 @@ static const struct npcx_i2c_timing_cfg npcx_20m_speed_confs[] = {
[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)
{
@ -779,6 +830,10 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
/* 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;
}
@ -793,6 +848,10 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
}
#ifdef CONFIG_PM
i2c_npcx_pm_policy_state_lock_put(dev, I2C_PM_POLICY_STATE_FLAG_TGT);
#endif /* CONFIG_PM */
return;
}
@ -857,7 +916,7 @@ static void i2c_ctrl_target_isr(const struct device *dev, uint8_t status)
status, data->port);
}
}
#endif
#endif /* CONFIG_I2C_TARGET */
/* I2C controller isr function */
static void i2c_ctrl_isr(const struct device *dev)
@ -874,7 +933,7 @@ static void i2c_ctrl_isr(const struct device *dev)
i2c_ctrl_target_isr(dev, status);
return;
}
#endif
#endif /* CONFIG_I2C_TARGET */
/* A 'Bus Error' has been identified */
if (IS_BIT_SET(status, NPCX_SMBST_BER)) {
@ -1073,6 +1132,7 @@ 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);
@ -1105,16 +1165,29 @@ int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
/* 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)
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;
/* No I2c module has been configured to target mode */
if (!atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
@ -1139,14 +1212,46 @@ int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
i2c_ctrl_irq_enable(i2c_dev, 1);
/* Disable irq of smb wake-up event */
if (IS_ENABLED(CONFIG_PM)) {
/* Disable SMB wake up detection */
npcx_i2c_target_start_wk_enable(idx_ctrl, false);
/* Disable start detect in IDLE */
inst->SMBCTL3 &= ~BIT(NPCX_SMBCTL3_IDL_START);
/* Disable SMB's MIWU interrupts */
npcx_miwu_irq_disable(&config->smb_wui);
}
i2c_ctrl_irq_enable(i2c_dev, 1);
/* Mark it as controller mode */
atomic_clear_bit(&data->flags, NPCX_I2C_FLAG_TARGET);
return 0;
}
#endif
static void i2c_target_wk_isr(const struct device *dev, struct npcx_wui *wui)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
/* Clear wake up detection event status */
npcx_i2c_target_clear_detection_event();
/* Reconfigure SMBCTL1 */
inst->SMBCTL1 |= BIT(NPCX_SMBCTL1_NMINTE) | BIT(NPCX_SMBCTL1_INTEN);
/*
* 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, int port)
@ -1160,7 +1265,7 @@ int npcx_i2c_ctrl_transfer(const struct device *i2c_dev, struct i2c_msg *msgs,
if (atomic_test_bit(&data->flags, NPCX_I2C_FLAG_TARGET)) {
return -EBUSY;
}
#endif
#endif /* CONFIG_I2C_TARGET */
/*
* suspend-to-idle stops SMB module clocks (derived from APB2/APB3), which must remain
@ -1285,6 +1390,21 @@ static int i2c_ctrl_init(const struct device *dev)
/* 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);
@ -1323,6 +1443,10 @@ static int i2c_ctrl_init(const struct device *dev)
.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; \

View file

@ -98,13 +98,14 @@ int npcx_i2c_ctrl_target_register(const struct device *i2c_dev,
*
* @param i2c_dev Pointer to the device structure for i2c controller instance.
* @param target_cfg Config struct used by the i2c target driver
* @param port Port index of selected i2c port.
*
* @retval 0 Is successful
* @retval -EBUSY If i2c transaction is proceeding.
* @retval -EINVAL If parameters are invalid
*/
int npcx_i2c_ctrl_target_unregister(const struct device *i2c_dev,
struct i2c_target_config *target_cfg);
struct i2c_target_config *target_cfg, uint8_t port);
#ifdef __cplusplus
}

View file

@ -171,7 +171,7 @@ static int i2c_npcx_target_unregister(const struct device *dev,
return -EIO;
}
return npcx_i2c_ctrl_target_unregister(config->i2c_ctrl, target_cfg);
return npcx_i2c_ctrl_target_unregister(config->i2c_ctrl, target_cfg, config->port);
}
#endif

View file

@ -105,6 +105,35 @@ void npcx_pinctrl_i2c_port_sel(int controller, int port)
}
}
void npcx_i2c_target_start_wk_enable(int controller, bool enable)
{
struct glue_reg *const inst_glue = HAL_GLUE_INST();
if (enable) {
/* Clear Start condition detection status */
inst_glue->SMB_SBD |= BIT(controller);
/* Enable wake up event assertion */
inst_glue->SMB_EEN |= BIT(controller);
} else {
/* Disable wake up event assertion */
inst_glue->SMB_EEN &= ~BIT(controller);
}
}
void npcx_i2c_target_clear_detection_event(void)
{
struct glue_reg *const inst_glue = HAL_GLUE_INST();
uint8_t een = inst_glue->SMB_EEN;
uint8_t sbd = inst_glue->SMB_SBD;
/* Clear Start condition detection status */
for (uint8_t i = 0; i < 8; i++) {
if ((een & BIT(i)) != 0 && (sbd & BIT(i)) != 0) {
inst_glue->SMB_SBD |= BIT(i);
}
}
}
int npcx_pinctrl_flash_write_protect_set(void)
{
struct scfg_reg *inst_scfg = HAL_SFCG_INST();

View file

@ -94,6 +94,20 @@ void npcx_host_interface_sel(enum npcx_hif_type hif_type);
*/
void npcx_i3c_target_sel(uint8_t module, bool enable);
/**
* @brief Enable smb controller wake up event detection in target mode
*
* @param controller i2c controller device
* @param enable True to enable wake up event detection, false to disable.
*/
void npcx_i2c_target_start_wk_enable(int controller, bool enable);
/**
* @brief Clear wake up event detection status in target mode
*
*/
void npcx_i2c_target_clear_detection_event(void);
#ifdef __cplusplus
}
#endif