drivers: ti: cc13xx/cc26xx: implement watchdog timer
New Zephyr WDT driver for TI CC13xx/CC26xx family. Supports interrupts & MCU soft reset on timeout. Signed-off-by: Stancu Florin <niflostancu@gmail.com>
This commit is contained in:
parent
e5adeef105
commit
236084df70
|
@ -11,6 +11,7 @@ zephyr_library_sources_ifdef(CONFIG_WWDGT_GD32 wdt_wwdgt_gd32.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_WDOG_CMSDK_APB wdt_cmsdk_apb.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_CC32XX wdt_cc32xx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_CC13XX_CC26XX wdt_cc13xx_cc26xx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_ESP32 wdt_esp32.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_GECKO wdt_gecko.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_ITE_IT8XXX2 wdt_ite_it8xxx2.c)
|
||||
|
|
|
@ -82,6 +82,8 @@ source "drivers/watchdog/Kconfig.npcx"
|
|||
|
||||
source "drivers/watchdog/Kconfig.cc32xx"
|
||||
|
||||
source "drivers/watchdog/Kconfig.cc13xx_cc26xx"
|
||||
|
||||
source "drivers/watchdog/Kconfig.it8xxx2"
|
||||
|
||||
source "drivers/watchdog/Kconfig.rpi_pico"
|
||||
|
|
20
drivers/watchdog/Kconfig.cc13xx_cc26xx
Normal file
20
drivers/watchdog/Kconfig.cc13xx_cc26xx
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright (c) 2021 Florin Stancu <niflostancu@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config WDT_CC13XX_CC26XX
|
||||
bool "Watchdog Driver for CC13xx / CC26xx family of MCUs"
|
||||
default y
|
||||
depends on DT_HAS_TI_CC13XX_CC26XX_WATCHDOG_ENABLED
|
||||
help
|
||||
Enable watchdog for CC13xx / CC26xx family of MCUs
|
||||
|
||||
config WDT_CC13XX_CC26XX_INITIAL_TIMEOUT
|
||||
int "Value for initial WDT timeout in ms"
|
||||
depends on WDT_CC13XX_CC26XX
|
||||
default 2000
|
||||
range 1 2863311
|
||||
help
|
||||
The CC13xx/CC26xx watchdog timer is sourced from the MCU clock
|
||||
using a fixed prescaler of 32.
|
||||
E.g., for the standard 48 MHz MCU clock, the following:
|
||||
0xFFFFFFFF / (48^9 / 32 / 1000) [ms]
|
252
drivers/watchdog/wdt_cc13xx_cc26xx.c
Normal file
252
drivers/watchdog/wdt_cc13xx_cc26xx.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* 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)
|
|
@ -150,6 +150,13 @@
|
|||
};
|
||||
};
|
||||
|
||||
wdt0: watchdog@40080000 {
|
||||
compatible = "ti,cc13xx-cc26xx-watchdog";
|
||||
reg = <0x40080000 0x1000>;
|
||||
interrupts = <14 0>; /* interrupt #30 = 14 + 16 */
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
adc0: adc@400cb008 {
|
||||
compatible = "ti,cc13xx-cc26xx-adc";
|
||||
reg = <0x400cb008 0x1>;
|
||||
|
|
20
dts/bindings/watchdog/ti,cc13xx-cc26xx-watchdog.yaml
Normal file
20
dts/bindings/watchdog/ti,cc13xx-cc26xx-watchdog.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright (c) 2022 Florin Stancu <niflostancu@gmail.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: TI CC13xx/CC26xx watchdog
|
||||
|
||||
compatible: "ti,cc13xx-cc26xx-watchdog"
|
||||
|
||||
include: base.yaml
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
interrupts:
|
||||
required: true
|
||||
|
||||
interrupt-nmi:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the watchdog triggers a Non-Maskable Interrupt or a standard one
|
Loading…
Reference in a new issue