blob: 02523dfeee2e701ce9f0349c4a4775e119c2fb6c [file] [log] [blame]
/*
* 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 <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);
#if !defined(_SILICON_LABS_32B_SERIES_2)
/* Ensure LE modules are clocked */
CMU_ClockEnable(config->clock, true);
#else
CMU_ClockSelectSet(config->clock, cmuSelect_ULFRCO);
#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)