zephyr/drivers/watchdog/wdt_nrfx.c
Michał Stasiak b578ffa49a drivers: watchdog: nrfx: add synchronization after stop
In order to ensure that watchdog channels are freed in proper
driver state, synchronization in form of simple loop needs
to be added after stopping. In no irq variant, it is already done
on nrfx level. NRFY function can be replaced by NRFX one in
the future.

Signed-off-by: Michał Stasiak <michal.stasiak@nordicsemi.no>
2025-01-14 08:59:04 +01:00

288 lines
6.9 KiB
C

/*
* Copyright (c) 2018, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/math_extras.h>
#include <nrfx_wdt.h>
#include <zephyr/drivers/watchdog.h>
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(wdt_nrfx);
#if !CONFIG_WDT_NRFX_NO_IRQ && NRF_WDT_HAS_STOP
#define WDT_NRFX_SYNC_STOP 1
#endif
struct wdt_nrfx_data {
wdt_callback_t m_callbacks[NRF_WDT_CHANNEL_NUMBER];
uint32_t m_timeout;
uint8_t m_allocated_channels;
bool enabled;
#if defined(WDT_NRFX_SYNC_STOP)
struct k_sem sync_stop;
#endif
};
struct wdt_nrfx_config {
nrfx_wdt_t wdt;
};
static int wdt_nrf_setup(const struct device *dev, uint8_t options)
{
const struct wdt_nrfx_config *config = dev->config;
struct wdt_nrfx_data *data = dev->data;
nrfx_err_t err_code;
nrfx_wdt_config_t wdt_config = {
.reload_value = data->m_timeout
};
#if NRF_WDT_HAS_STOP
wdt_config.behaviour |= NRF_WDT_BEHAVIOUR_STOP_ENABLE_MASK;
#endif
if (!(options & WDT_OPT_PAUSE_IN_SLEEP)) {
wdt_config.behaviour |= NRF_WDT_BEHAVIOUR_RUN_SLEEP_MASK;
}
if (!(options & WDT_OPT_PAUSE_HALTED_BY_DBG)) {
wdt_config.behaviour |= NRF_WDT_BEHAVIOUR_RUN_HALT_MASK;
}
err_code = nrfx_wdt_reconfigure(&config->wdt, &wdt_config);
if (err_code != NRFX_SUCCESS) {
return -EBUSY;
}
nrfx_wdt_enable(&config->wdt);
data->enabled = true;
return 0;
}
static int wdt_nrf_disable(const struct device *dev)
{
#if NRFX_WDT_HAS_STOP
const struct wdt_nrfx_config *config = dev->config;
struct wdt_nrfx_data *data = dev->data;
nrfx_err_t err_code;
int channel_id;
err_code = nrfx_wdt_stop(&config->wdt);
if (err_code != NRFX_SUCCESS) {
/* This can only happen if wdt_nrf_setup() is not called first. */
return -EFAULT;
}
#if defined(WDT_NRFX_SYNC_STOP)
k_sem_take(&data->sync_stop, K_FOREVER);
#endif
nrfx_wdt_channels_free(&config->wdt);
for (channel_id = 0; channel_id < data->m_allocated_channels; channel_id++) {
data->m_callbacks[channel_id] = NULL;
}
data->m_allocated_channels = 0;
data->enabled = false;
return 0;
#else
ARG_UNUSED(dev);
return -EPERM;
#endif
}
static int wdt_nrf_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
const struct wdt_nrfx_config *config = dev->config;
struct wdt_nrfx_data *data = dev->data;
nrfx_err_t err_code;
nrfx_wdt_channel_id channel_id;
if (data->enabled) {
return -EBUSY;
}
if (cfg->flags != WDT_FLAG_RESET_SOC) {
return -ENOTSUP;
}
if (cfg->window.min != 0U) {
return -EINVAL;
}
if (data->m_allocated_channels == 0U) {
/* According to relevant Product Specifications, watchdogs
* in all nRF chips can use reload values (determining
* the timeout) from range 0xF-0xFFFFFFFF given in 32768 Hz
* clock ticks. This makes the allowed range of 0x1-0x07CFFFFF
* in milliseconds. Check if the provided value is within
* this range.
*/
if ((cfg->window.max == 0U) || (cfg->window.max > 0x07CFFFFF)) {
return -EINVAL;
}
/* Save timeout value from first registered watchdog channel. */
data->m_timeout = cfg->window.max;
} else if (cfg->window.max != data->m_timeout) {
return -EINVAL;
}
err_code = nrfx_wdt_channel_alloc(&config->wdt,
&channel_id);
if (err_code == NRFX_ERROR_NO_MEM) {
return -ENOMEM;
}
if (cfg->callback != NULL) {
data->m_callbacks[channel_id] = cfg->callback;
}
data->m_allocated_channels++;
return channel_id;
}
static int wdt_nrf_feed(const struct device *dev, int channel_id)
{
const struct wdt_nrfx_config *config = dev->config;
struct wdt_nrfx_data *data = dev->data;
if ((channel_id >= data->m_allocated_channels) || (channel_id < 0)) {
return -EINVAL;
}
if (!data->enabled) {
/* Watchdog is not running so does not need to be fed */
return -EAGAIN;
}
nrfx_wdt_channel_feed(&config->wdt,
(nrfx_wdt_channel_id)channel_id);
return 0;
}
static DEVICE_API(wdt, wdt_nrfx_driver_api) = {
.setup = wdt_nrf_setup,
.disable = wdt_nrf_disable,
.install_timeout = wdt_nrf_install_timeout,
.feed = wdt_nrf_feed,
};
static void wdt_event_handler(const struct device *dev, nrf_wdt_event_t event_type,
uint32_t requests, void *p_context)
{
struct wdt_nrfx_data *data = dev->data;
#if defined(WDT_NRFX_SYNC_STOP)
if (event_type == NRF_WDT_EVENT_STOPPED) {
k_sem_give(&data->sync_stop);
}
#else
(void)event_type;
#endif
(void)p_context;
while (requests) {
uint8_t i = u32_count_trailing_zeros(requests);
if (data->m_callbacks[i]) {
data->m_callbacks[i](dev, i);
}
requests &= ~BIT(i);
}
}
#define WDT(idx) DT_NODELABEL(wdt##idx)
#define WDT_NRFX_WDT_IRQ(idx) \
COND_CODE_1(CONFIG_WDT_NRFX_NO_IRQ, \
(), \
(IRQ_CONNECT(DT_IRQN(WDT(idx)), DT_IRQ(WDT(idx), priority), \
nrfx_isr, nrfx_wdt_##idx##_irq_handler, 0)))
#define WDT_NRFX_WDT_DEVICE(idx) \
static void wdt_##idx##_event_handler(nrf_wdt_event_t event_type, \
uint32_t requests, \
void *p_context) \
{ \
wdt_event_handler(DEVICE_DT_GET(WDT(idx)), event_type, \
requests, p_context); \
} \
static int wdt_##idx##_init(const struct device *dev) \
{ \
const struct wdt_nrfx_config *config = dev->config; \
nrfx_err_t err_code; \
WDT_NRFX_WDT_IRQ(idx); \
err_code = nrfx_wdt_init(&config->wdt, \
NULL, \
IS_ENABLED(CONFIG_WDT_NRFX_NO_IRQ) \
? NULL \
: wdt_##idx##_event_handler, \
NULL); \
if (err_code != NRFX_SUCCESS) { \
return -EBUSY; \
} \
return 0; \
} \
static struct wdt_nrfx_data wdt_##idx##_data = { \
IF_ENABLED(WDT_NRFX_SYNC_STOP, \
(.sync_stop = Z_SEM_INITIALIZER( \
wdt_##idx##_data.sync_stop, 0, 1),)) \
}; \
static const struct wdt_nrfx_config wdt_##idx##z_config = { \
.wdt = NRFX_WDT_INSTANCE(idx), \
}; \
DEVICE_DT_DEFINE(WDT(idx), \
wdt_##idx##_init, \
NULL, \
&wdt_##idx##_data, \
&wdt_##idx##z_config, \
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&wdt_nrfx_driver_api)
#ifdef CONFIG_HAS_HW_NRF_WDT0
WDT_NRFX_WDT_DEVICE(0);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT1
WDT_NRFX_WDT_DEVICE(1);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT30
WDT_NRFX_WDT_DEVICE(30);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT31
WDT_NRFX_WDT_DEVICE(31);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT010
WDT_NRFX_WDT_DEVICE(010);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT011
WDT_NRFX_WDT_DEVICE(011);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT130
WDT_NRFX_WDT_DEVICE(130);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT131
WDT_NRFX_WDT_DEVICE(131);
#endif
#ifdef CONFIG_HAS_HW_NRF_WDT132
WDT_NRFX_WDT_DEVICE(132);
#endif