180 lines
4.3 KiB
C
180 lines
4.3 KiB
C
/*
|
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/drivers/counter.h>
|
|
#include <zephyr/logging/log_ctrl.h>
|
|
|
|
#define WDT_CHANNEL_COUNT DT_PROP(DT_WDT_COUNTER, num_channels)
|
|
#define DT_WDT_COUNTER DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_counter_watchdog)
|
|
|
|
#define WDT_SUPPORTED_CFG_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC)
|
|
|
|
extern void sys_arch_reboot(int type);
|
|
|
|
struct wdt_counter_data {
|
|
wdt_callback_t callback[CONFIG_WDT_COUNTER_CH_COUNT];
|
|
uint32_t timeout[CONFIG_WDT_COUNTER_CH_COUNT];
|
|
uint8_t flags[CONFIG_WDT_COUNTER_CH_COUNT];
|
|
uint8_t alloc_cnt;
|
|
};
|
|
|
|
static struct wdt_counter_data wdt_data;
|
|
|
|
struct wdt_counter_config {
|
|
const struct device *counter;
|
|
};
|
|
|
|
static int wdt_counter_setup(const struct device *dev, uint8_t options)
|
|
{
|
|
const struct wdt_counter_config *config = dev->config;
|
|
const struct device *counter = config->counter;
|
|
|
|
if ((options & WDT_OPT_PAUSE_IN_SLEEP) || (options & WDT_OPT_PAUSE_HALTED_BY_DBG)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return counter_start(counter);
|
|
}
|
|
|
|
static int wdt_counter_disable(const struct device *dev)
|
|
{
|
|
const struct wdt_counter_config *config = dev->config;
|
|
const struct device *counter = config->counter;
|
|
|
|
return counter_stop(counter);
|
|
}
|
|
|
|
static void counter_alarm_callback(const struct device *dev,
|
|
uint8_t chan_id, uint32_t ticks,
|
|
void *user_data)
|
|
{
|
|
const struct device *wdt_dev = user_data;
|
|
struct wdt_counter_data *data = wdt_dev->data;
|
|
|
|
counter_stop(dev);
|
|
if (data->callback[chan_id]) {
|
|
data->callback[chan_id](wdt_dev, chan_id);
|
|
}
|
|
|
|
if (data->flags[chan_id] & WDT_FLAG_RESET_SOC) {
|
|
LOG_PANIC();
|
|
sys_arch_reboot(0);
|
|
}
|
|
}
|
|
|
|
static int timeout_set(const struct device *dev, int chan_id, bool cancel)
|
|
{
|
|
const struct wdt_counter_config *config = dev->config;
|
|
struct wdt_counter_data *data = dev->data;
|
|
const struct device *counter = config->counter;
|
|
struct counter_alarm_cfg alarm_cfg = {
|
|
.callback = counter_alarm_callback,
|
|
.ticks = data->timeout[chan_id],
|
|
.user_data = (void *)dev,
|
|
.flags = 0
|
|
};
|
|
|
|
if (cancel) {
|
|
int err = counter_cancel_channel_alarm(counter, chan_id);
|
|
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return counter_set_channel_alarm(counter, chan_id, &alarm_cfg);
|
|
}
|
|
|
|
static int wdt_counter_install_timeout(const struct device *dev,
|
|
const struct wdt_timeout_cfg *cfg)
|
|
{
|
|
struct wdt_counter_data *data = dev->data;
|
|
const struct wdt_counter_config *config = dev->config;
|
|
const struct device *counter = config->counter;
|
|
int chan_id;
|
|
|
|
if (!device_is_ready(counter)) {
|
|
return -EIO;
|
|
}
|
|
|
|
uint32_t max_timeout = counter_get_top_value(counter) -
|
|
counter_get_guard_period(counter,
|
|
COUNTER_GUARD_PERIOD_LATE_TO_SET);
|
|
uint32_t timeout_ticks = counter_us_to_ticks(counter, cfg->window.max * 1000);
|
|
|
|
if (cfg->flags & ~WDT_SUPPORTED_CFG_FLAGS) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (cfg->window.min != 0U) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (timeout_ticks > max_timeout || timeout_ticks == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data->alloc_cnt == 0) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
data->alloc_cnt--;
|
|
chan_id = data->alloc_cnt;
|
|
data->timeout[chan_id] = timeout_ticks;
|
|
data->callback[chan_id] = cfg->callback;
|
|
data->flags[chan_id] = cfg->flags;
|
|
|
|
int err = timeout_set(dev, chan_id, false);
|
|
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
return chan_id;
|
|
}
|
|
|
|
static int wdt_counter_feed(const struct device *dev, int chan_id)
|
|
{
|
|
const struct wdt_counter_config *config = dev->config;
|
|
|
|
if (chan_id > counter_get_num_of_channels(config->counter)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Move alarm further in time. */
|
|
return timeout_set(dev, chan_id, true);
|
|
}
|
|
|
|
static const struct wdt_driver_api wdt_counter_driver_api = {
|
|
.setup = wdt_counter_setup,
|
|
.disable = wdt_counter_disable,
|
|
.install_timeout = wdt_counter_install_timeout,
|
|
.feed = wdt_counter_feed,
|
|
};
|
|
|
|
static const struct wdt_counter_config wdt_counter_config = {
|
|
.counter = DEVICE_DT_GET(DT_PHANDLE(DT_WDT_COUNTER, counter)),
|
|
};
|
|
|
|
|
|
static int wdt_counter_init(const struct device *dev)
|
|
{
|
|
const struct wdt_counter_config *config = dev->config;
|
|
struct wdt_counter_data *data = dev->data;
|
|
uint8_t ch_cnt = counter_get_num_of_channels(config->counter);
|
|
|
|
data->alloc_cnt = MIN(ch_cnt, CONFIG_WDT_COUNTER_CH_COUNT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_DEFINE(DT_WDT_COUNTER, wdt_counter_init, NULL,
|
|
&wdt_data, &wdt_counter_config,
|
|
POST_KERNEL,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
|
&wdt_counter_driver_api);
|