blob: f70b1d2e2322a094c714653c6dd912b64fffb85e [file] [log] [blame]
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_ewm
#include <zephyr/drivers/watchdog.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wdt_nxp_ewm);
#define NXP_EWM_FEED_MAGIC_NUMBER 0x2CB4
#define NXP_EWM_MAX_TIMEOUT_WINDOW 0xFE
struct nxp_ewm_config {
EWM_Type *base;
void (*irq_config_func)(const struct device *dev);
bool is_input_enabled;
bool is_input_active_high;
uint8_t clk_sel;
uint8_t clk_divider;
};
struct nxp_ewm_data {
struct wdt_timeout_cfg timeout_cfg;
bool is_watchdog_setup;
};
static int nxp_ewm_setup(const struct device *dev, uint8_t options)
{
const struct nxp_ewm_config *config = dev->config;
struct nxp_ewm_data *data = dev->data;
EWM_Type *base = config->base;
if (data->is_watchdog_setup) {
/* Watchdog cannot be re-configured after enabled. */
return -EBUSY;
}
if (options) {
/* Unable to halt counter during debugging */
return -ENOTSUP;
}
data->is_watchdog_setup = true;
base->CMPL = EWM_CMPL_COMPAREL(data->timeout_cfg.window.min);
base->CMPH = EWM_CMPH_COMPAREH(data->timeout_cfg.window.max);
/*
* base->CTRL should be the last thing touched due to
* the small watchdog window time.
* After this write, only the INTEN bit is writable until reset.
*
* EWM_CTRL_INTEN enables the interrupt signal
* EWM_CTRL_EWMEN enables the watchdog.
*/
base->CTRL |= EWM_CTRL_INEN(config->is_input_enabled) |
EWM_CTRL_ASSIN(config->is_input_active_high) |
EWM_CTRL_INTEN(1) | EWM_CTRL_EWMEN(1);
return 0;
}
static int nxp_ewm_disable(const struct device *dev)
{
struct nxp_ewm_data *data = dev->data;
if (!data->is_watchdog_setup) {
return -EFAULT;
}
return -EPERM;
}
static int nxp_ewm_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
struct nxp_ewm_data *data = dev->data;
if (cfg->flags) {
return -ENOTSUP;
}
if (data->is_watchdog_setup) {
return -ENOMEM;
}
if (cfg && cfg->window.max <= NXP_EWM_MAX_TIMEOUT_WINDOW &&
cfg->window.min <= cfg->window.max &&
cfg->window.max > 0 &&
cfg->window.min >= 0) {
data->timeout_cfg.window = cfg->window;
} else {
return -EINVAL;
}
#if defined(CONFIG_WDT_MULTISTAGE)
if (cfg->next) {
return -EINVAL;
}
#endif
if (cfg->callback) {
data->timeout_cfg.callback = cfg->callback;
}
return 0;
}
static int nxp_ewm_feed(const struct device *dev, int channel_id)
{
ARG_UNUSED(channel_id);
const struct nxp_ewm_config *config = dev->config;
EWM_Type *base = config->base;
unsigned int key = irq_lock();
base->SERV = EWM_SERV_SERVICE(NXP_EWM_FEED_MAGIC_NUMBER);
base->SERV = EWM_SERV_SERVICE((uint8_t)(NXP_EWM_FEED_MAGIC_NUMBER >> 8));
irq_unlock(key);
return 0;
}
static void nxp_ewm_isr(const struct device *dev)
{
const struct nxp_ewm_config *config = dev->config;
struct nxp_ewm_data *data = dev->data;
EWM_Type *base = config->base;
base->CTRL &= (~EWM_CTRL_INTEN_MASK);
if (data->timeout_cfg.callback) {
data->timeout_cfg.callback(dev, 0);
}
}
static int nxp_ewm_init(const struct device *dev)
{
const struct nxp_ewm_config *config = dev->config;
EWM_Type *base = config->base;
#if DT_INST_NODE_HAS_PROP(0, clk_sel)
/* Set clock select value for CLKCTRL register */
switch (config->clk_sel) {
case 0:
base->CLKCTRL = EWM_CLKCTRL_CLKSEL(0);
break;
case 1:
base->CLKCTRL = EWM_CLKCTRL_CLKSEL(1);
break;
case 2:
base->CLKCTRL = EWM_CLKCTRL_CLKSEL(2);
break;
case 3:
base->CLKCTRL = EWM_CLKCTRL_CLKSEL(3);
break;
default:
LOG_ERR("Invalid clock select value: %d", config->clk_sel);
return -EINVAL;
}
#endif /* DT_INST_NODE_HAS_PROP(0, clk_sel) */
#if DT_INST_NODE_HAS_PROP(0, clk_divider)
if (config->clk_divider >= 0 && config->clk_divider <= 0xFF) {
base->CLKPRESCALER = EWM_CLKPRESCALER_CLK_DIV(config->clk_divider);
}
#endif /* DT_INST_NODE_HAS_PROP(0, clk_divider) */
config->irq_config_func(dev);
return 0;
}
static DEVICE_API(wdt, nxp_ewm_api) = {
.setup = nxp_ewm_setup,
.disable = nxp_ewm_disable,
.install_timeout = nxp_ewm_install_timeout,
.feed = nxp_ewm_feed,
};
#define CLK_SEL_DEFAULT 0
#define EWM_CONFIG_CLK_SEL_INIT(n)\
.clk_sel = COND_CODE_1(DT_INST_NODE_HAS_PROP(n, clk_sel), \
(DT_INST_ENUM_IDX(n, clk_sel)), \
(CLK_SEL_DEFAULT)),
#define WDT_EWM_INIT(n) \
static void nxp_ewm_config_func_##n(const struct device *dev); \
\
static const struct nxp_ewm_config nxp_ewm_config_##n = { \
.base = (EWM_Type *)DT_INST_REG_ADDR(n), \
.irq_config_func = nxp_ewm_config_func_##n, \
.is_input_enabled = DT_INST_PROP(n, input_trigger_en), \
.is_input_active_high = \
DT_INST_PROP(n, input_trigger_active_high), \
EWM_CONFIG_CLK_SEL_INIT(n) \
.clk_divider = DT_INST_PROP_OR(n, clk_divider, 0), \
}; \
\
static struct nxp_ewm_data nxp_ewm_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, \
nxp_ewm_init, \
NULL, \
&nxp_ewm_data_##n, &nxp_ewm_config_##n, \
POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&nxp_ewm_api); \
\
static void nxp_ewm_config_func_##n(const struct device *dev) \
{ \
ARG_UNUSED(dev); \
\
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
nxp_ewm_isr, DEVICE_DT_INST_GET(n), 0); \
\
irq_enable(DT_INST_IRQN(n)); \
}
DT_INST_FOREACH_STATUS_OKAY(WDT_EWM_INIT)