drivers: watchdog: support Nuvoton numaker WWDT
Add Nuvoton numaker series window watchdog feature. Signed-off-by: cyliang tw <cyliang@nuvoton.com>
This commit is contained in:
parent
a943d7fd53
commit
7cb3f47ae9
6 changed files with 343 additions and 0 deletions
|
@ -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_OPENTITAN wdt_opentitan.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_WDT_AMBIQ wdt_ambiq.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_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_DW wdt_dw.c wdt_dw_common.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c)
|
zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c)
|
||||||
|
|
|
@ -122,4 +122,6 @@ source "drivers/watchdog/Kconfig.shell"
|
||||||
|
|
||||||
source "drivers/watchdog/Kconfig.xmc4xxx"
|
source "drivers/watchdog/Kconfig.xmc4xxx"
|
||||||
|
|
||||||
|
source "drivers/watchdog/Kconfig.numaker"
|
||||||
|
|
||||||
endif # WATCHDOG
|
endif # WATCHDOG
|
||||||
|
|
13
drivers/watchdog/Kconfig.numaker
Normal file
13
drivers/watchdog/Kconfig.numaker
Normal file
|
@ -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.
|
301
drivers/watchdog/wdt_wwdt_numaker.c
Normal file
301
drivers/watchdog/wdt_wwdt_numaker.c
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Nuvoton Technology Corporation.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT nuvoton_numaker_wwdt
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/drivers/reset.h>
|
||||||
|
#include <zephyr/drivers/clock_control.h>
|
||||||
|
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
|
||||||
|
#include <zephyr/drivers/watchdog.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <soc.h>
|
||||||
|
#include <NuMicro.h>
|
||||||
|
|
||||||
|
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);
|
|
@ -616,6 +616,14 @@
|
||||||
num-bidir-endpoints = <25>;
|
num-bidir-endpoints = <25>;
|
||||||
disallow-iso-in-out-same-number;
|
disallow-iso-in-out-same-number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
18
dts/bindings/watchdog/nuvoton,numaker-wwdt.yaml
Normal file
18
dts/bindings/watchdog/nuvoton,numaker-wwdt.yaml
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue