253 lines
6.5 KiB
C
253 lines
6.5 KiB
C
/*
|
|
* Copyright (c) 2022 Florin Stancu <niflostancu@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT ti_cc13xx_cc26xx_watchdog
|
|
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/irq.h>
|
|
#include <soc.h>
|
|
#include <errno.h>
|
|
|
|
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(wdt_cc13xx_cc26xx);
|
|
|
|
/* Driverlib includes */
|
|
#include <driverlib/watchdog.h>
|
|
|
|
|
|
/*
|
|
* TI CC13xx/CC26xx watchdog is a 32-bit timer that runs on the MCU clock
|
|
* with a fixed 32 divider.
|
|
*
|
|
* For the default MCU frequency of 48MHz:
|
|
* 1ms = (48e6 / 32 / 1000) = 1500 ticks
|
|
* Max. value = 2^32 / 1500 ~= 2863311 ms
|
|
*
|
|
* The watchdog will issue reset only on second in turn time-out (if the timer
|
|
* or the interrupt aren't reset after the first time-out). By default, regular
|
|
* interrupt is generated but platform supports also NMI (can be enabled by
|
|
* setting the `interrupt-nmi` boolean DT property).
|
|
*/
|
|
|
|
#define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency)
|
|
#define WATCHDOG_DIV_RATIO 32
|
|
#define WATCHDOG_MS_RATIO (CPU_FREQ / WATCHDOG_DIV_RATIO / 1000)
|
|
#define WATCHDOG_MAX_RELOAD_MS (0xFFFFFFFFu / WATCHDOG_MS_RATIO)
|
|
#define WATCHDOG_MS_TO_TICKS(_ms) ((_ms) * WATCHDOG_MS_RATIO)
|
|
|
|
struct wdt_cc13xx_cc26xx_data {
|
|
uint8_t enabled;
|
|
uint32_t reload;
|
|
wdt_callback_t cb;
|
|
uint8_t flags;
|
|
};
|
|
|
|
struct wdt_cc13xx_cc26xx_cfg {
|
|
uint32_t reg;
|
|
uint8_t irq_nmi;
|
|
void (*irq_cfg_func)(void);
|
|
};
|
|
|
|
static int wdt_cc13xx_cc26xx_install_timeout(const struct device *dev,
|
|
const struct wdt_timeout_cfg *cfg)
|
|
{
|
|
struct wdt_cc13xx_cc26xx_data *data = dev->data;
|
|
|
|
/* window watchdog not supported */
|
|
if (cfg->window.min != 0U || cfg->window.max == 0U) {
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* Note: since this SoC doesn't define CONFIG_WDT_MULTISTAGE, we don't need to
|
|
* specifically check for it and return ENOTSUP
|
|
*/
|
|
|
|
if (cfg->window.max > WATCHDOG_MAX_RELOAD_MS) {
|
|
return -EINVAL;
|
|
}
|
|
data->reload = WATCHDOG_MS_TO_TICKS(cfg->window.max);
|
|
data->cb = cfg->callback;
|
|
data->flags = cfg->flags;
|
|
LOG_DBG("raw reload value: %d", data->reload);
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_cc13xx_cc26xx_setup(const struct device *dev, uint8_t options)
|
|
{
|
|
const struct wdt_cc13xx_cc26xx_cfg *config = dev->config;
|
|
struct wdt_cc13xx_cc26xx_data *data = dev->data;
|
|
|
|
/*
|
|
* Note: don't check if watchdog is already enabled, an application might
|
|
* want to dynamically re-configure its options (e.g., decrease the reload
|
|
* value for critical sections).
|
|
*/
|
|
|
|
WatchdogUnlock();
|
|
|
|
/* clear any previous interrupt flags */
|
|
WatchdogIntClear();
|
|
|
|
/* Stall the WDT counter when halted by debugger */
|
|
if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) {
|
|
WatchdogStallEnable();
|
|
} else {
|
|
WatchdogStallDisable();
|
|
}
|
|
/*
|
|
* According to TI's datasheets, the WDT is paused in STANDBY mode,
|
|
* so we simply continue with the setup => don't do this check:
|
|
* > if (options & WDT_OPT_PAUSE_IN_SLEEP) {
|
|
* > return -ENOTSUP;
|
|
* > }
|
|
*/
|
|
|
|
/* raw reload value was computed by `_install_timeout()` */
|
|
WatchdogReloadSet(data->reload);
|
|
|
|
/* use the Device Tree-configured interrupt type */
|
|
if (config->irq_nmi) {
|
|
LOG_DBG("NMI enabled");
|
|
WatchdogIntTypeSet(WATCHDOG_INT_TYPE_NMI);
|
|
} else {
|
|
WatchdogIntTypeSet(WATCHDOG_INT_TYPE_INT);
|
|
}
|
|
|
|
switch ((data->flags & WDT_FLAG_RESET_MASK)) {
|
|
case WDT_FLAG_RESET_NONE:
|
|
LOG_DBG("reset disabled");
|
|
WatchdogResetDisable();
|
|
break;
|
|
case WDT_FLAG_RESET_SOC:
|
|
LOG_DBG("reset enabled");
|
|
WatchdogResetEnable();
|
|
break;
|
|
default:
|
|
WatchdogLock();
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
data->enabled = 1;
|
|
WatchdogEnable();
|
|
WatchdogLock();
|
|
|
|
LOG_DBG("done");
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_cc13xx_cc26xx_disable(const struct device *dev)
|
|
{
|
|
struct wdt_cc13xx_cc26xx_data *data = dev->data;
|
|
|
|
if (!WatchdogRunning()) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
/*
|
|
* Node: once started, the watchdog timer cannot be stopped!
|
|
* All we can do is disable the timeout reset, but the interrupt
|
|
* will be triggered if it was enabled (though it won't trigger the
|
|
* user callback due to `enabled` being unsed)!
|
|
*/
|
|
data->enabled = 0;
|
|
WatchdogUnlock();
|
|
WatchdogResetDisable();
|
|
WatchdogLock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_cc13xx_cc26xx_feed(const struct device *dev, int channel_id)
|
|
{
|
|
struct wdt_cc13xx_cc26xx_data *data = dev->data;
|
|
|
|
WatchdogUnlock();
|
|
WatchdogIntClear();
|
|
WatchdogReloadSet(data->reload);
|
|
WatchdogLock();
|
|
LOG_DBG("feed %i", data->reload);
|
|
return 0;
|
|
}
|
|
|
|
static void wdt_cc13xx_cc26xx_isr(const struct device *dev)
|
|
{
|
|
struct wdt_cc13xx_cc26xx_data *data = dev->data;
|
|
|
|
/* Simulate the watchdog being disabled: don't call the handler. */
|
|
if (!data->enabled) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Note: don't clear the interrupt here, leave it for the callback
|
|
* to decide (by calling `_feed()`)
|
|
*/
|
|
|
|
LOG_DBG("ISR");
|
|
if (data->cb) {
|
|
data->cb(dev, 0);
|
|
}
|
|
}
|
|
|
|
static int wdt_cc13xx_cc26xx_init(const struct device *dev)
|
|
{
|
|
const struct wdt_cc13xx_cc26xx_cfg *config = dev->config;
|
|
uint8_t options = 0;
|
|
|
|
LOG_DBG("init");
|
|
config->irq_cfg_func();
|
|
|
|
if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG
|
|
/* when CONFIG_DEBUG is enabled, pause the WDT during debugging */
|
|
options = WDT_OPT_PAUSE_HALTED_BY_DBG;
|
|
#endif /* CONFIG_DEBUG */
|
|
|
|
return wdt_cc13xx_cc26xx_setup(dev, options);
|
|
}
|
|
|
|
static const struct wdt_driver_api wdt_cc13xx_cc26xx_api = {
|
|
.setup = wdt_cc13xx_cc26xx_setup,
|
|
.disable = wdt_cc13xx_cc26xx_disable,
|
|
.install_timeout = wdt_cc13xx_cc26xx_install_timeout,
|
|
.feed = wdt_cc13xx_cc26xx_feed,
|
|
};
|
|
|
|
#define CC13XX_CC26XX_WDT_INIT(index) \
|
|
static void wdt_cc13xx_cc26xx_irq_cfg_##index(void) \
|
|
{ \
|
|
if (DT_INST_PROP(index, interrupt_nmi)) { \
|
|
return; /* NMI interrupt is used */ \
|
|
} \
|
|
IRQ_CONNECT(DT_INST_IRQN(index), \
|
|
DT_INST_IRQ(index, priority), \
|
|
wdt_cc13xx_cc26xx_isr, DEVICE_DT_INST_GET(index), 0); \
|
|
irq_enable(DT_INST_IRQN(index)); \
|
|
} \
|
|
static struct wdt_cc13xx_cc26xx_data wdt_cc13xx_cc26xx_data_##index = { \
|
|
.reload = WATCHDOG_MS_TO_TICKS( \
|
|
CONFIG_WDT_CC13XX_CC26XX_INITIAL_TIMEOUT), \
|
|
.cb = NULL, \
|
|
.flags = 0, \
|
|
}; \
|
|
static struct wdt_cc13xx_cc26xx_cfg wdt_cc13xx_cc26xx_cfg_##index = { \
|
|
.reg = DT_INST_REG_ADDR(index), \
|
|
.irq_nmi = DT_INST_PROP(index, interrupt_nmi), \
|
|
.irq_cfg_func = wdt_cc13xx_cc26xx_irq_cfg_##index, \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(index, \
|
|
wdt_cc13xx_cc26xx_init, NULL, \
|
|
&wdt_cc13xx_cc26xx_data_##index, \
|
|
&wdt_cc13xx_cc26xx_cfg_##index, \
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
&wdt_cc13xx_cc26xx_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(CC13XX_CC26XX_WDT_INIT)
|