diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 7f58a5461a930a7..9414b865b55126d 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -40,6 +40,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_INFINEON_CAT1 wdt_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_WDT_OPENTITAN wdt_opentitan.c) zephyr_library_sources_ifdef(CONFIG_WDT_AMBIQ wdt_ambiq.c) zephyr_library_sources_ifdef(CONFIG_WDT_XMC4XXX wdt_xmc4xxx.c) +zephyr_library_sources_ifdef(CONFIG_WWDT_NUMAKER wdt_wwdt_numaker.c) zephyr_library_sources_ifdef(CONFIG_WDT_DW wdt_dw.c wdt_dw_common.c) zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 2ee5545f39e02c8..f10dc5582e8ba6a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -122,4 +122,6 @@ source "drivers/watchdog/Kconfig.shell" source "drivers/watchdog/Kconfig.xmc4xxx" +source "drivers/watchdog/Kconfig.numaker" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.numaker b/drivers/watchdog/Kconfig.numaker new file mode 100644 index 000000000000000..bc0ac984c7a3d0d --- /dev/null +++ b/drivers/watchdog/Kconfig.numaker @@ -0,0 +1,13 @@ +# NUMAKER Watchdog Driver configuration options + +# Copyright (c) 2024 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config WWDT_NUMAKER + bool "Nuvoton NUMAKER MCU Window Watchdog driver" + default y + depends on DT_HAS_NUVOTON_NUMAKER_WWDT_ENABLED + help + This option enables the Watchdog driver for Nuvoton NuMaker family of + processors. + Say y if you wish to enable NuMaker WWDT. diff --git a/drivers/watchdog/wdt_wwdt_numaker.c b/drivers/watchdog/wdt_wwdt_numaker.c new file mode 100644 index 000000000000000..0b7aa42ad469f61 --- /dev/null +++ b/drivers/watchdog/wdt_wwdt_numaker.c @@ -0,0 +1,301 @@ +/* + * 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("%s", __func__); + /* 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("%s counter=%d", __func__, 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("%s", __func__); + /* 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("%s", __func__); + 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("%s CNT=%d, CTL=0x%x", __func__, 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("%s CNT=%d", __func__, 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); diff --git a/dts/arm/nuvoton/m46x.dtsi b/dts/arm/nuvoton/m46x.dtsi index d73a888d21b4019..4d45b31395975a8 100644 --- a/dts/arm/nuvoton/m46x.dtsi +++ b/dts/arm/nuvoton/m46x.dtsi @@ -603,6 +603,14 @@ status = "disabled"; #io-channel-cells = <1>; }; + + wwdt: watchdog@40040100 { + compatible = "nuvoton,numaker-wwdt"; + reg = <0x40040100 0x10>; + interrupts = <9 0>; + clocks = <&pcc NUMAKER_WWDT_MODULE NUMAKER_CLK_CLKSEL1_WWDTSEL_LIRC 0>; + status = "disabled"; + }; }; }; diff --git a/dts/bindings/watchdog/nuvoton,numaker-wwdt.yaml b/dts/bindings/watchdog/nuvoton,numaker-wwdt.yaml new file mode 100644 index 000000000000000..058e45100d75f6f --- /dev/null +++ b/dts/bindings/watchdog/nuvoton,numaker-wwdt.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton, NuMaker window watchdog timer + +compatible: "nuvoton,numaker-wwdt" + +include: base.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clocks: + required: true