309 lines
7.3 KiB
C
309 lines
7.3 KiB
C
/*
|
|
* Copyright (c) 2019 Interay Solutions B.V.
|
|
* Copyright (c) 2019 Oane Kingma
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT silabs_gecko_wdog
|
|
|
|
#include <soc.h>
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/irq.h>
|
|
#include <em_wdog.h>
|
|
#include <em_cmu.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(wdt_gecko, CONFIG_WDT_LOG_LEVEL);
|
|
|
|
#ifdef cmuClock_CORELE
|
|
#define CLOCK_DEF(id) cmuClock_CORELE
|
|
#else
|
|
#define CLOCK_DEF(id) cmuClock_WDOG##id
|
|
#endif /* cmuClock_CORELE */
|
|
#define CLOCK_ID(id) CLOCK_DEF(id)
|
|
|
|
/* Defines maximum WDOG_CTRL.PERSEL value which is used by the watchdog module
|
|
* to select its timeout period.
|
|
*/
|
|
#define WDT_GECKO_MAX_PERIOD_SELECT_VALUE 15
|
|
|
|
/* Device constant configuration parameters */
|
|
struct wdt_gecko_cfg {
|
|
WDOG_TypeDef *base;
|
|
CMU_Clock_TypeDef clock;
|
|
void (*irq_cfg_func)(void);
|
|
};
|
|
|
|
struct wdt_gecko_data {
|
|
wdt_callback_t callback;
|
|
WDOG_Init_TypeDef wdog_config;
|
|
bool timeout_installed;
|
|
};
|
|
|
|
static uint32_t wdt_gecko_get_timeout_from_persel(int perSel)
|
|
{
|
|
return (8 << perSel) + 1;
|
|
}
|
|
|
|
/* Find the rounded up value of cycles for supplied timeout. When using ULFRCO
|
|
* (default), 1 cycle is 1 ms +/- 12%.
|
|
*/
|
|
static int wdt_gecko_get_persel_from_timeout(uint32_t timeout)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < WDT_GECKO_MAX_PERIOD_SELECT_VALUE; idx++) {
|
|
if (wdt_gecko_get_timeout_from_persel(idx) >= timeout) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
static int wdt_gecko_convert_window(uint32_t window, uint32_t period)
|
|
{
|
|
int idx = 0;
|
|
uint32_t incr_val, comp_val;
|
|
|
|
incr_val = period / 8;
|
|
comp_val = 0; /* Initially 0, disable */
|
|
|
|
/* Valid window settings range from 12.5% of the calculated
|
|
* timeout period up to 87.5% (= 7 * 12.5%)
|
|
*/
|
|
while (idx < 7) {
|
|
if (window > comp_val) {
|
|
comp_val += incr_val;
|
|
idx++;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
static int wdt_gecko_setup(const struct device *dev, uint8_t options)
|
|
{
|
|
const struct wdt_gecko_cfg *config = dev->config;
|
|
struct wdt_gecko_data *data = dev->data;
|
|
WDOG_TypeDef *wdog = config->base;
|
|
|
|
if (!data->timeout_installed) {
|
|
LOG_ERR("No valid timeouts installed");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->wdog_config.em2Run =
|
|
(options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
|
|
data->wdog_config.em3Run =
|
|
(options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
|
|
|
|
data->wdog_config.debugRun =
|
|
(options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U;
|
|
|
|
if (data->callback != NULL) {
|
|
/* Interrupt mode for window */
|
|
/* Clear possible lingering interrupts */
|
|
WDOGn_IntClear(wdog, WDOG_IEN_TOUT);
|
|
/* Enable timeout interrupt */
|
|
WDOGn_IntEnable(wdog, WDOG_IEN_TOUT);
|
|
} else {
|
|
/* Disable timeout interrupt */
|
|
WDOGn_IntDisable(wdog, WDOG_IEN_TOUT);
|
|
}
|
|
|
|
/* Watchdog is started after initialization */
|
|
WDOGn_Init(wdog, &data->wdog_config);
|
|
LOG_DBG("Setup the watchdog");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_gecko_disable(const struct device *dev)
|
|
{
|
|
const struct wdt_gecko_cfg *config = dev->config;
|
|
struct wdt_gecko_data *data = dev->data;
|
|
WDOG_TypeDef *wdog = config->base;
|
|
|
|
WDOGn_Enable(wdog, false);
|
|
data->timeout_installed = false;
|
|
LOG_DBG("Disabled the watchdog");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_gecko_install_timeout(const struct device *dev,
|
|
const struct wdt_timeout_cfg *cfg)
|
|
{
|
|
struct wdt_gecko_data *data = dev->data;
|
|
data->wdog_config = (WDOG_Init_TypeDef)WDOG_INIT_DEFAULT;
|
|
uint32_t installed_timeout;
|
|
|
|
if (data->timeout_installed) {
|
|
LOG_ERR("No more timeouts can be installed");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if ((cfg->window.max < wdt_gecko_get_timeout_from_persel(0)) ||
|
|
(cfg->window.max > wdt_gecko_get_timeout_from_persel(
|
|
WDT_GECKO_MAX_PERIOD_SELECT_VALUE))) {
|
|
LOG_ERR("Upper limit timeout out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(_WDOG_CTRL_CLKSEL_MASK)
|
|
data->wdog_config.clkSel = wdogClkSelULFRCO;
|
|
#endif
|
|
|
|
data->wdog_config.perSel = (WDOG_PeriodSel_TypeDef)
|
|
wdt_gecko_get_persel_from_timeout(cfg->window.max);
|
|
|
|
installed_timeout = wdt_gecko_get_timeout_from_persel(
|
|
data->wdog_config.perSel);
|
|
LOG_INF("Installed timeout value: %u", installed_timeout);
|
|
|
|
if (cfg->window.min > 0) {
|
|
/* Window mode. Use rounded up timeout value to
|
|
* calculate minimum window setting.
|
|
*/
|
|
data->wdog_config.winSel = (WDOG_WinSel_TypeDef)
|
|
wdt_gecko_convert_window(cfg->window.min,
|
|
installed_timeout);
|
|
|
|
LOG_INF("Installed window value: %u",
|
|
(installed_timeout / 8) * data->wdog_config.winSel);
|
|
} else {
|
|
/* Normal mode */
|
|
data->wdog_config.winSel = wdogIllegalWindowDisable;
|
|
}
|
|
|
|
/* Set mode of watchdog and callback */
|
|
switch (cfg->flags) {
|
|
case WDT_FLAG_RESET_SOC:
|
|
case WDT_FLAG_RESET_CPU_CORE:
|
|
if (cfg->callback != NULL) {
|
|
LOG_ERR("Reset mode with callback not supported\n");
|
|
return -ENOTSUP;
|
|
}
|
|
data->wdog_config.resetDisable = false;
|
|
LOG_DBG("Configuring reset CPU/SoC mode\n");
|
|
break;
|
|
|
|
case WDT_FLAG_RESET_NONE:
|
|
data->wdog_config.resetDisable = true;
|
|
data->callback = cfg->callback;
|
|
LOG_DBG("Configuring non-reset mode\n");
|
|
break;
|
|
|
|
default:
|
|
LOG_ERR("Unsupported watchdog config flag");
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->timeout_installed = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wdt_gecko_feed(const struct device *dev, int channel_id)
|
|
{
|
|
const struct wdt_gecko_cfg *config = dev->config;
|
|
WDOG_TypeDef *wdog = config->base;
|
|
|
|
if (channel_id != 0) {
|
|
LOG_ERR("Invalid channel id");
|
|
return -EINVAL;
|
|
}
|
|
|
|
WDOGn_Feed(wdog);
|
|
LOG_DBG("Fed the watchdog");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void wdt_gecko_isr(const struct device *dev)
|
|
{
|
|
const struct wdt_gecko_cfg *config = dev->config;
|
|
struct wdt_gecko_data *data = dev->data;
|
|
WDOG_TypeDef *wdog = config->base;
|
|
uint32_t flags;
|
|
|
|
/* Clear IRQ flags */
|
|
flags = WDOGn_IntGet(wdog);
|
|
WDOGn_IntClear(wdog, flags);
|
|
|
|
if (data->callback != NULL) {
|
|
data->callback(dev, 0);
|
|
}
|
|
}
|
|
|
|
static int wdt_gecko_init(const struct device *dev)
|
|
{
|
|
const struct wdt_gecko_cfg *config = dev->config;
|
|
|
|
#ifdef CONFIG_WDT_DISABLE_AT_BOOT
|
|
/* Ignore any errors */
|
|
wdt_gecko_disable(dev);
|
|
#endif
|
|
|
|
/* Enable ULFRCO (1KHz) oscillator */
|
|
CMU_OscillatorEnable(cmuOsc_ULFRCO, true, false);
|
|
|
|
/* Ensure LE modules are clocked */
|
|
CMU_ClockEnable(config->clock, true);
|
|
|
|
#if defined(_SILICON_LABS_32B_SERIES_2)
|
|
CMU_ClockSelectSet(config->clock, cmuSelect_ULFRCO);
|
|
/* Enable Watchdog clock. */
|
|
CMU_ClockEnable(cmuClock_WDOG0, true);
|
|
#endif
|
|
|
|
/* Enable IRQs */
|
|
config->irq_cfg_func();
|
|
|
|
LOG_INF("Device %s initialized", dev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct wdt_driver_api wdt_gecko_driver_api = {
|
|
.setup = wdt_gecko_setup,
|
|
.disable = wdt_gecko_disable,
|
|
.install_timeout = wdt_gecko_install_timeout,
|
|
.feed = wdt_gecko_feed,
|
|
};
|
|
|
|
#define GECKO_WDT_INIT(index) \
|
|
\
|
|
static void wdt_gecko_cfg_func_##index(void); \
|
|
\
|
|
static const struct wdt_gecko_cfg wdt_gecko_cfg_##index = { \
|
|
.base = (WDOG_TypeDef *) \
|
|
DT_INST_REG_ADDR(index),\
|
|
.clock = CLOCK_ID(DT_INST_PROP(index, peripheral_id)), \
|
|
.irq_cfg_func = wdt_gecko_cfg_func_##index, \
|
|
}; \
|
|
static struct wdt_gecko_data wdt_gecko_data_##index; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(index, \
|
|
&wdt_gecko_init, NULL, \
|
|
&wdt_gecko_data_##index, \
|
|
&wdt_gecko_cfg_##index, POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
&wdt_gecko_driver_api); \
|
|
\
|
|
static void wdt_gecko_cfg_func_##index(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(index), \
|
|
DT_INST_IRQ(index, priority),\
|
|
wdt_gecko_isr, DEVICE_DT_INST_GET(index), 0); \
|
|
irq_enable(DT_INST_IRQN(index)); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(GECKO_WDT_INIT)
|