diff --git a/drivers/i2c/i2c_npcx_controller.c b/drivers/i2c/i2c_npcx_controller.c index 8cffca7dfa5..6c6cac01c7c 100644 --- a/drivers/i2c/i2c_npcx_controller.c +++ b/drivers/i2c/i2c_npcx_controller.c @@ -73,6 +73,9 @@ #include #include #include +#include "soc_miwu.h" +#include "soc_pins.h" +#include "soc_power.h" #include #include @@ -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); @@ -1316,24 +1436,28 @@ static int i2c_ctrl_init(const struct device *dev) } -#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); \ - \ +#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) diff --git a/drivers/i2c/i2c_npcx_controller.h b/drivers/i2c/i2c_npcx_controller.h index 6155f1a1499..dc80aba386f 100644 --- a/drivers/i2c/i2c_npcx_controller.h +++ b/drivers/i2c/i2c_npcx_controller.h @@ -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 } diff --git a/drivers/i2c/i2c_npcx_port.c b/drivers/i2c/i2c_npcx_port.c index 33f77e62d62..37505787319 100644 --- a/drivers/i2c/i2c_npcx_port.c +++ b/drivers/i2c/i2c_npcx_port.c @@ -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 diff --git a/soc/nuvoton/npcx/common/scfg.c b/soc/nuvoton/npcx/common/scfg.c index b57476225bb..b29d7356b55 100644 --- a/soc/nuvoton/npcx/common/scfg.c +++ b/soc/nuvoton/npcx/common/scfg.c @@ -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(); diff --git a/soc/nuvoton/npcx/common/soc_pins.h b/soc/nuvoton/npcx/common/soc_pins.h index 143aad6b58d..dcd403aa306 100644 --- a/soc/nuvoton/npcx/common/soc_pins.h +++ b/soc/nuvoton/npcx/common/soc_pins.h @@ -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