/* * Copyright (c) 2024 Nuvoton Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nuvoton_numaker_wwdt #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(wwdt_numaker, CONFIG_WDT_LOG_LEVEL); #define NUMAKER_PRESCALER_MAX 15U #define NUMAKER_COUNTER_MAX 0x3eU #define NUMAKER_COUNTER_MIN 0x01U /* Device config */ struct wwdt_numaker_config { /* wdt base address */ WWDT_T *wwdt_base; uint32_t clk_modidx; uint32_t clk_src; uint32_t clk_div; const struct device *clk_dev; }; struct wwdt_numaker_data { wdt_callback_t cb; bool timeout_valid; /* watchdog timeout in milliseconds */ uint32_t timeout; uint32_t prescaler; uint32_t counter; }; static int m_wwdt_numaker_clk_get_rate(const struct wwdt_numaker_config *cfg, uint32_t *rate) { if (cfg->clk_src == CLK_CLKSEL1_WWDTSEL_LIRC) { *rate = __LIRC / (cfg->clk_div + 1); } else { /* clock source is from HCLK, CLK_CLKSEL1_WWDTSEL_HCLK_DIV2048 */ SystemCoreClockUpdate(); *rate = CLK_GetHCLKFreq() / 2048 / (cfg->clk_div + 1); } return 0; } /* Convert watchdog clock to nearest ms (rounded up) */ static uint32_t m_wwdt_numaker_calc_ms(const struct device *dev, uint32_t pow2) { const struct wwdt_numaker_config *cfg = dev->config; uint32_t clk_freq; uint32_t prescale_clks; uint32_t period_ms; m_wwdt_numaker_clk_get_rate(cfg, &clk_freq); prescale_clks = (1 << pow2) * 64; period_ms = DIV_ROUND_UP(prescale_clks * MSEC_PER_SEC, clk_freq); return period_ms; } static int m_wwdt_numaker_calc_window(const struct device *dev, const struct wdt_window *win, uint32_t *timeout, uint32_t *prescaler, uint32_t *counter) { uint32_t pow2; uint32_t gap; /* Find nearest period value (rounded up) */ for (pow2 = 0U; pow2 <= NUMAKER_PRESCALER_MAX; pow2++) { *timeout = m_wwdt_numaker_calc_ms(dev, pow2); if (*timeout >= win->max) { *prescaler = pow2 << WWDT_CTL_PSCSEL_Pos; if (win->min == 0U) { *counter = NUMAKER_COUNTER_MAX; } else { gap = DIV_ROUND_UP(win->min * NUMAKER_COUNTER_MAX, *timeout); *counter = NUMAKER_COUNTER_MAX - gap; if (*counter < NUMAKER_COUNTER_MIN) { *counter = NUMAKER_COUNTER_MIN; } } return 0; } } return -EINVAL; } static int wwdt_numaker_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg) { struct wwdt_numaker_data *data = dev->data; const struct wwdt_numaker_config *config = dev->config; uint32_t timeout; uint32_t prescaler; uint32_t counter; LOG_DBG(""); /* Validate watchdog already running */ if (config->wwdt_base->CTL & WWDT_CTL_WWDTEN_Msk) { LOG_ERR("watchdog is busy"); return -EBUSY; } if (cfg->window.max == 0U) { LOG_ERR("window.max should be non-zero"); return -EINVAL; } if (m_wwdt_numaker_calc_window(dev, &cfg->window, &timeout, &prescaler, &counter) != 0) { LOG_ERR("window.max is out of range"); return -EINVAL; } LOG_DBG("counter=%d", counter); data->timeout = timeout; data->prescaler = prescaler; data->counter = counter; data->cb = cfg->callback; data->timeout_valid = true; return 0; } static int wwdt_numaker_disable(const struct device *dev) { struct wwdt_numaker_data *data = dev->data; const struct wwdt_numaker_config *cfg = dev->config; WWDT_T *wwdt_base = cfg->wwdt_base; LOG_DBG(""); /* stop counting */ wwdt_base->CTL &= ~WWDT_CTL_WWDTEN_Msk; /* disable interrupt enable bit */ wwdt_base->CTL &= ~WWDT_CTL_INTEN_Msk; /* disable interrupt */ irq_disable(DT_INST_IRQN(0)); data->timeout_valid = false; return 0; } static int wwdt_numaker_setup(const struct device *dev, uint8_t options) { struct wwdt_numaker_data *data = dev->data; const struct wwdt_numaker_config *cfg = dev->config; WWDT_T *wwdt_base = cfg->wwdt_base; uint32_t dbg_mask = 0U; LOG_DBG(""); irq_disable(DT_INST_IRQN(0)); /* Validate watchdog already running */ if (wwdt_base->CTL & WWDT_CTL_WWDTEN_Msk) { LOG_ERR("watchdog is busy"); return -EBUSY; } if (!data->timeout_valid) { LOG_ERR("No valid timeout installed"); return -EINVAL; } if (options & WDT_OPT_PAUSE_IN_SLEEP) { LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP is not supported"); return -ENOTSUP; } if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { dbg_mask = WWDT_CTL_ICEDEBUG_Msk; } /* Clear WWDT Reset & Compared Match Interrupt System Flag */ wwdt_base->STATUS = WWDT_STATUS_WWDTRF_Msk | WWDT_STATUS_WWDTIF_Msk; /* Open WWDT and start counting */ wwdt_base->CTL = data->prescaler | (data->counter << WWDT_CTL_CMPDAT_Pos) | WWDT_CTL_INTEN_Msk | WWDT_CTL_WWDTEN_Msk | dbg_mask; irq_enable(DT_INST_IRQN(0)); return 0; } static int wwdt_numaker_feed(const struct device *dev, int channel_id) { const struct wwdt_numaker_config *cfg = dev->config; WWDT_T *wwdt_base = cfg->wwdt_base; LOG_DBG("CNT=%d, CTL=0x%x", wwdt_base->CNT, wwdt_base->CTL); ARG_UNUSED(channel_id); /* Reload WWDT Counter */ wwdt_base->RLDCNT = WWDT_RELOAD_WORD; return 0; } static void wwdt_numaker_isr(const struct device *dev) { struct wwdt_numaker_data *data = dev->data; const struct wwdt_numaker_config *cfg = dev->config; WWDT_T *wwdt_base = cfg->wwdt_base; LOG_DBG("CNT=%d", wwdt_base->CNT); if (wwdt_base->STATUS & WWDT_STATUS_WWDTIF_Msk) { /* Clear WWDT Compared Match Interrupt Flag */ wwdt_base->STATUS = WWDT_STATUS_WWDTIF_Msk; if (data->cb != NULL) { data->cb(dev, 0); } } } static const struct wdt_driver_api wwdt_numaker_api = { .setup = wwdt_numaker_setup, .disable = wwdt_numaker_disable, .install_timeout = wwdt_numaker_install_timeout, .feed = wwdt_numaker_feed, }; static int wwdt_numaker_init(const struct device *dev) { const struct wwdt_numaker_config *cfg = dev->config; struct numaker_scc_subsys scc_subsys; int err; SYS_UnlockReg(); irq_disable(DT_INST_IRQN(0)); /* CLK controller */ memset(&scc_subsys, 0x00, sizeof(scc_subsys)); scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC; scc_subsys.pcc.clk_modidx = cfg->clk_modidx; scc_subsys.pcc.clk_src = cfg->clk_src; scc_subsys.pcc.clk_div = cfg->clk_div; /* Equivalent to CLK_EnableModuleClock() */ err = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys); if (err != 0) { goto done; } /* Equivalent to CLK_SetModuleClock() */ err = clock_control_configure(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL); if (err != 0) { goto done; } /* Enable NVIC */ IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), wwdt_numaker_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); done: SYS_LockReg(); return err; } /* Set config based on DTS */ static struct wwdt_numaker_config wwdt_numaker_cfg_inst = { .wwdt_base = (WWDT_T *)DT_INST_REG_ADDR(0), .clk_modidx = DT_INST_CLOCKS_CELL(0, clock_module_index), .clk_src = DT_INST_CLOCKS_CELL(0, clock_source), .clk_div = DT_INST_CLOCKS_CELL(0, clock_divider), .clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(0))), }; static struct wwdt_numaker_data wwdt_numaker_data_inst; DEVICE_DT_INST_DEFINE(0, wwdt_numaker_init, NULL, &wwdt_numaker_data_inst, &wwdt_numaker_cfg_inst, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wwdt_numaker_api);