blob: e038bb0d4d59f94fb02e0dae3ccf8f65bdd1d22b [file] [log] [blame]
/*
* Copyright (C) 2023 SLB
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT infineon_xmc4xxx_watchdog
#include <errno.h>
#include <stdint.h>
#include <soc.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/irq.h>
#include <xmc_scu.h>
#include <xmc_wdt.h>
struct wdt_xmc4xxx_dev_data {
wdt_callback_t cb;
uint8_t mode;
bool timeout_valid;
bool is_serviced;
};
/* When the watchdog counter rolls over, the SCU will generate a */
/* pre-warning event which gets routed to the ISR below. If the */
/* watchdog is not serviced, the SCU will only reset the MCU */
/* after the second time the counter rolls over. */
/* Hence the reset will only happen after 2*cfg->window.max have elapsed. */
/* We could potentially manually reset the MCU, but this way the context */
/* information (i.e. that reset happened because of a watchdog) is lost. */
static void wdt_xmc4xxx_isr(const struct device *dev)
{
struct wdt_xmc4xxx_dev_data *data = dev->data;
uint32_t event = XMC_SCU_INTERUPT_GetEventStatus();
/* todo add interrupt controller? */
if ((event & XMC_SCU_INTERRUPT_EVENT_WDT_WARN) == 0) {
return;
}
/* this is a level triggered interrupt. the event must be cleared */
XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_WDT_WARN);
data->is_serviced = false;
if (data->cb) {
data->cb(dev, 0);
}
/* Ensure that watchdog is serviced if RESET_NONE mode is used */
if (data->mode == WDT_FLAG_RESET_NONE && !data->is_serviced) {
XMC_WDT_Service();
}
XMC_WDT_ClearAlarm();
}
static int wdt_xmc4xxx_disable(const struct device *dev)
{
struct wdt_xmc4xxx_dev_data *data = dev->data;
XMC_WDT_Stop();
XMC_WDT_Disable();
data->timeout_valid = false;
return 0;
}
static int wdt_xmc4xxx_setup(const struct device *dev, uint8_t options)
{
struct wdt_xmc4xxx_dev_data *data = dev->data;
if (!data->timeout_valid) {
return -EINVAL;
}
if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) {
SCU_CLK->SLEEPCR &= ~XMC_SCU_CLOCK_SLEEP_MODE_CONFIG_ENABLE_WDT;
} else {
SCU_CLK->SLEEPCR |= XMC_SCU_CLOCK_SLEEP_MODE_CONFIG_ENABLE_WDT;
}
if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0) {
XMC_WDT_SetDebugMode(XMC_WDT_DEBUG_MODE_STOP);
} else {
XMC_WDT_SetDebugMode(XMC_WDT_DEBUG_MODE_RUN);
}
XMC_WDT_Start();
return 0;
}
static int wdt_xmc4xxx_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
{
XMC_WDT_CONFIG_t wdt_config = {0};
struct wdt_xmc4xxx_dev_data *data = dev->data;
uint32_t wdt_clock;
/* disable the watchdog if timeout was already installed */
if (data->timeout_valid) {
wdt_xmc4xxx_disable(dev);
data->timeout_valid = false;
}
if (cfg->window.min != 0 || cfg->window.max == 0) {
return -EINVAL;
}
wdt_clock = XMC_SCU_CLOCK_GetWdtClockFrequency();
if ((uint64_t)cfg->window.max * wdt_clock / 1000 > UINT32_MAX) {
return -EINVAL;
}
wdt_config.window_upper_bound = (uint64_t)cfg->window.max * wdt_clock / 1000;
XMC_WDT_Init(&wdt_config);
XMC_WDT_SetDebugMode(XMC_WDT_MODE_PREWARNING);
XMC_SCU_INTERRUPT_EnableEvent(XMC_SCU_INTERRUPT_EVENT_WDT_WARN);
if (cfg->flags == WDT_FLAG_RESET_NONE && cfg->callback == NULL) {
return -EINVAL;
}
data->cb = cfg->callback;
data->mode = cfg->flags;
data->timeout_valid = true;
return 0;
}
static int wdt_xmc4xxx_feed(const struct device *dev, int channel_id)
{
ARG_UNUSED(channel_id);
struct wdt_xmc4xxx_dev_data *data = dev->data;
XMC_WDT_Service();
data->is_serviced = true;
return 0;
}
static const struct wdt_driver_api wdt_xmc4xxx_api = {
.setup = wdt_xmc4xxx_setup,
.disable = wdt_xmc4xxx_disable,
.install_timeout = wdt_xmc4xxx_install_timeout,
.feed = wdt_xmc4xxx_feed,
};
static struct wdt_xmc4xxx_dev_data wdt_xmc4xxx_data;
static void wdt_xmc4xxx_irq_config(void)
{
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), wdt_xmc4xxx_isr,
DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
}
static int wdt_xmc4xxx_init(const struct device *dev)
{
wdt_xmc4xxx_irq_config();
#ifdef CONFIG_WDT_DISABLE_AT_BOOT
return 0;
#else
int ret;
const struct wdt_timeout_cfg cfg = {.window.max = CONFIG_WDT_XMC4XXX_DEFAULT_TIMEOUT_MAX_MS,
.flags = WDT_FLAG_RESET_SOC};
ret = wdt_xmc4xxx_install_timeout(dev, &cfg);
if (ret < 0) {
return ret;
}
return wdt_xmc4xxx_setup(dev, WDT_OPT_PAUSE_HALTED_BY_DBG);
#endif
}
DEVICE_DT_INST_DEFINE(0, wdt_xmc4xxx_init, NULL, &wdt_xmc4xxx_data, NULL, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xmc4xxx_api);