Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * 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 <drivers/watchdog.h>
#include <em_wdog.h>
#include <em_cmu.h>

#include <logging/log.h>
LOG_MODULE_REGISTER(wdt_gecko, CONFIG_WDT_LOG_LEVEL);

/* 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;
	void (*irq_cfg_func)(void);
};

struct wdt_gecko_data {
	wdt_callback_t callback;
	WDOG_Init_TypeDef wdog_config;
	bool timeout_installed;
};

#define DEV_NAME(dev) ((dev)->name)
#define DEV_DATA(dev) \
	((struct wdt_gecko_data *)(dev)->driver_data)
#define DEV_CFG(dev) \
	((const struct wdt_gecko_cfg *)(dev)->config_info)

static u32_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(u32_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(u32_t window, u32_t period)
{
	int idx = 0;
	u32_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(struct device *dev, u8_t options)
{
	const struct wdt_gecko_cfg *config = DEV_CFG(dev);
	struct wdt_gecko_data *data = DEV_DATA(dev);
	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(struct device *dev)
{
	const struct wdt_gecko_cfg *config = DEV_CFG(dev);
	struct wdt_gecko_data *data = DEV_DATA(dev);
	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(struct device *dev,
				     const struct wdt_timeout_cfg *cfg)
{
	struct wdt_gecko_data *data = DEV_DATA(dev);
	data->wdog_config = (WDOG_Init_TypeDef)WDOG_INIT_DEFAULT;
	u32_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(struct device *dev, int channel_id)
{
	const struct wdt_gecko_cfg *config = DEV_CFG(dev);
	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(void *arg)
{
	struct device *dev = (struct device *)arg;
	const struct wdt_gecko_cfg *config = DEV_CFG(dev);
	struct wdt_gecko_data *data = DEV_DATA(dev);
	WDOG_TypeDef *wdog = config->base;
	u32_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(struct device *dev)
{
	const struct wdt_gecko_cfg *config = DEV_CFG(dev);

#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(cmuClock_CORELE, true);

	/* Enable IRQs */
	config->irq_cfg_func();

	LOG_INF("Device %s initialized", DEV_NAME(dev));

	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),\
		.irq_cfg_func = wdt_gecko_cfg_func_##index,		\
	};								\
	static struct wdt_gecko_data wdt_gecko_data_##index;		\
									\
	DEVICE_AND_API_INIT(wdt_##index,				\
				DT_INST_LABEL(index),\
				&wdt_gecko_init, &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_GET(wdt_##index), 0);	\
		irq_enable(DT_INST_IRQN(index));	\
	}

DT_INST_FOREACH_STATUS_OKAY(GECKO_WDT_INIT)